第10版
10th Edition
ISBN 978-1-925904-15-4
ISBN 978-1-925904-15-4
© 2018-2021 克雷格·斯科特
© 2018-2021 by Craig Scott
未经作者明确书面许可,不得以任何方式或形式复制本书或其任何部分,但以下特定例外:
This book or any portion thereof may not be reproduced in any manner or form without the express written permission of the author, with the following specific exceptions:
本文中包含的建议和策略可能并不适合所有情况。出售本书的前提是,作者对本书中的建议所产生的结果不承担任何责任。
The advice and strategies contained within this work may not be suitable for every situation. This work is sold with the understanding that the author is not held responsible for the results accrued from the advice in this book.
早在 2016 年左右,我就对缺乏学习如何使用 CMake 的已发表材料感到惊讶。对于那些愿意探索的人来说,官方参考文档是一个有用的资源,但作为一种渐进式、结构化的方式学习 CMake 的方式,它并不理想。有一些维基和个人网站包含一些有用的内容,但也有许多包含过时或有问题的建议和示例。存在明显的差距,这意味着 CMake 的新手很难学习良好的实践,导致许多人变得不知所措或沮丧。
Back around 2016, I was surprised at the lack of published material for learning how to use CMake. The official reference documentation was a useful resource for those willing to go exploring, but as a way of learning CMake in a progressive, structured manner it was not ideal. There were some wikis and personal websites that had some useful contents, but there were also many that contained out-of-date or questionable advice and examples. There was a distinct gap, which meant those new to CMake had a hard time learning good practices, leading to many becoming overwhelmed or frustrated.
当时,我一直在写一些博客文章,以便利用业余时间做一些更有成效的事情,并加深我自己在软件开发方面的技术知识。我经常写一些我在工作中与同事互动或在我自己的发展活动中出现的领域,我发现这对其他人来说既有益又有用。当这种模式不断重复时,写书的想法就诞生了。一晃两年半,这本书就诞生了。
At the time, I had been writing some blog articles to do something more productive with my spare time and to deepen my own technical knowledge around software development. I frequently wrote about areas that came up in my interaction with colleagues at work or in my own development activities and I found this to be both rewarding and useful to others. As that pattern repeated itself, the idea of writing a book was born. Fast-forward two and a half years and the result is this book.
一路走来,有一个经典的关键时刻,我现在回想起来都带着某种程度的乐趣。一位同事对他希望 CMake 拥有的一个特定功能感到遗憾。它钻进了我的大脑并在那里呆了几个月,直到有一天我决定探索自己添加该功能有多么困难。最终形成了测试装置功能,该功能现在已成为 CMake 的一部分,但更重要的是,我在做出这一贡献时所获得的积极体验给我留下了深刻的印象。适当的人员、工具和流程使该项目的工作真正成为一种乐趣。从那时起,我更加深入地参与其中,现在担任志愿者共同维护者的角色。
Along the way, there was a classic pivotal moment which I now look back on with some degree of amusement. A colleague bemoaned a particular feature that he wished CMake had. It burrowed its way into my brain and sat there for a few months until one day I decided to explore how hard it would be to add that feature myself. That culminated in the test fixtures feature that is now a part of CMake, but more importantly I was really struck by the positive experience I had while making that contribution. The people, the tools and the processes in place made working on the project truly a pleasure. From there, I became more deeply involved and now fulfill the role of volunteer co-maintainer.
只有当你感谢所有为你的书出版过程做出贡献的人时,你才会意识到有多少人参与其中。没有其他人的慷慨、耐心和洞察力,这样的工作就不会发生,如果没有挑战、测试和返工,它也不会成功。它依赖于那些足够善良(有时是在不知不觉中!)参与这些活动的人,就像它依赖于作者一样,我对这些人的善良和智慧感激不尽。
It is only when you come to thank all those who have contributed to the process of getting your book released that you realize just how many people have been involved. A work like this doesn’t happen without the generosity, patience and insight of others, nor does it succeed without being challenged, tested and reworked. It relies on those who were kind enough to (sometimes unknowingly!) get involved in these activities as much as it does on the author and I cannot thank these people enough for their kindness and wisdom.
如果没有 Kitware 及其员工过去和现在的持续支持,CMake 社区不会像今天这样强大和充满活力。我想特别提及 CMake 项目负责人 Brad King,他通过包容和鼓励的方式来处理新的 CMake 贡献者,让像我这样的人非常受欢迎,并感到有能力参与其中。通过观察他与开发人员和用户互动的方式、提供强有力的领导力并营造尊重他人的环境,我个人从他身上学到了很多东西。不言而喻,多年来 CMake 的许多贡献者的努力也值得赞扬,他们的努力通常是纯粹自愿的。我对这么多人所做的贡献以及对软件开发世界的积极影响感到谦卑。
The CMake community wouldn’t be as strong and as vibrant as it is today without the ongoing support of Kitware and its staff, both past and present. I’d like to make special mention of Brad King, the CMake project leader, who through his inclusive and encouraging approach to handling new CMake contributors has made people like myself very welcome and feel empowered to get involved. I have personally learned much from him just by observing the way he interacts with developers and users, providing strong leadership and fostering an environment of respect for others. It also goes without saying that the many contributors to CMake over the years also deserve much praise for their efforts, often made on a purely voluntary basis. I’m humbled by the scale of contributions that have been made by so many and by the positive impact on the world of software development.
许多人慷慨地同意审阅本书中的材料,如果没有他们,技术准确性和可读性都会受到影响。任何剩余的错误和缺陷都是我自己的。CMake 开发人员 Gregor Jasny 和 Christian Pfeiffer 在第一份出版物的整个审核过程中做出了宝贵的贡献。我衷心感谢他们的建议和见解。还要感谢尼尔斯·格拉迪茨 (Nils Gladitz) 的投入,特别是在第一份出版物发布之前的短时间内。我还要感谢我以前的同事马特·博尔格 (Matt Bolger) 和拉克兰·赫瑟顿 (Lachlan Hetherton),他们都提供了建设性的反馈,并提醒我以新的眼光看待问题的重要性。
A number of people generously agreed to review the material in this book, without whom both technical accuracy and readability would have suffered. Any remaining errors and deficiencies are squarely my own. Fellow CMake developers Gregor Jasny and Christian Pfeiffer were valuable contributors throughout the review process for the first publication. I am truly grateful for their suggestions and insights. Thanks also to Nils Gladitz for his input, especially at such short notice before the first publication. I’d also like to thank my past colleagues, Matt Bolger and Lachlan Hetherton, both of whom provided constructive feedback and reminded me of the importance of a fresh set of eyes.
许多人慷慨地审阅了后续版本的章节。其中许多人也是 CMake 的贡献者或维护者,我很感谢他们花时间审阅本书并改进 CMake 本身。感谢 Cristian Adam 审阅了第 30 章“构建性能”和 第 31 章“使用 Qt”,并感谢 Tobias Hunger、Alexandru Croitor、Joerg Bornemann、Sebastian Holtermann 和 Burkhard Stubert,他们都在不同阶段审阅了有关 Qt 的材料。Alex Turbov 和 Marc Chevrier 还为第七版中的新增和更新材料提供了宝贵的意见和建议。
A number of people have generously reviewed chapters for subsequent editions. Many of these people are also CMake contributors or maintainers, and I am grateful for both their time reviewing this book and in improving CMake itself. Thanks to Cristian Adam for reviewing Chapter 30, Build Performance and Chapter 31, Working With Qt, and to Tobias Hunger, Alexandru Croitor, Joerg Bornemann, Sebastian Holtermann and Burkhard Stubert, all of whom also reviewed the material on Qt at various stages. Alex Turbov and Marc Chevrier also provided valuable comments and suggestions for new and updated material in the 7th edition.
我们也非常感谢读者提供的有用反馈。我想特别提及 Declan Moran 和 Ganesh A. Hegde 的努力,他们都强调了一些改进和修正。
The helpful feedback received from readers is also much appreciated. I’d like to make particular mention of the efforts of Declan Moran and Ganesh A. Hegde, both of whom highlighted a number of improvements and corrections.
特别值得一提的是我过去的同事迈克·韦克(Mike Wake)。本书中的大部分内容都是在真实的、积极开发的生产项目中经过反复推敲和测试的。关于如何从可用性和稳健性角度改进事物,已经出现了错误的转折和许多技术讨论。他为解决这些问题提供空间和鼓励,以及他愿意忍受一些短期(有时不是那么短期)的痛苦,这些都是将许多流程和技术提炼为有效的方法的重要组成部分。实践。我也非常感谢在一些压力更大、筋疲力尽的时期,在正确的时间及时提供的建议和鼓励。
A special mention is deserved for my past colleague Mike Wake. Much of the material in this book was thrashed out and tested in real, actively developed production projects. There have been wrong turns and many technical discussions on how to improve things from both a usability and robustness perspective. His support in giving the space and encouragement to work through these things and his willingness to wear some short-term (and sometimes not-so-short-term) pain have been an instrumental part of distilling many processes and techniques down to what works in practice. I am also very grateful for the timely words of advice and encouragement delivered at just the right time during some of the more stressful and exhausting periods.
我还要向 Asciidoctor(用于编译和准备本书的软件)背后的人员表示感谢。尽管这些材料的规模、复杂性和技术性质,我一直惊讶于它如何使自助出版不仅成为一种可行的选择,而且成为一种令人愉快的体验,而且几乎没有实际限制。现在从作者到读者的路径比我最初开始写这本书时要短得多、简单得多。感谢您提供这么棒的工具!
I would also like to express my gratitude to the people behind Asciidoctor, the software used to compile and prepare this book. Despite the size, complexity and technical nature of the material, I have been constantly amazed at how it has made self-publishing not just a viable option, but also an enjoyable experience with surprisingly few practical limitations. The path from author to reader is now so much shorter and simpler than when I initially began work on this book. Thanks for the awesome tool!
这本书的封面和网站上的一些支持材料是我对图形设计比我更好的眼光和理解的结果。对于我的朋友兼设计师 V,你以某种方式设法理解我随机的、互不相关的想法和相互冲突的片段的方式仍然让我感到困惑。我不明白你是怎么做到的,但我很喜欢最终的结果!
The book’s cover and some of the supporting material on the website are the result of a better eye and understanding of graphical design than my own. To my friend and designer, V, the way you somehow managed to make sense of my random, disconnected ideas and conflicting snippets still baffles me. I don’t understand how you do it, but I do like the end result!
在每本书的致谢部分,作者总是感谢家人和配偶,这是有充分理由的。你需要付出巨大的理解和牺牲来忍受你的疲劳,你无法做很多普通人可以做的事情,以及你在一个项目上投入比他们更多的时间的不合理决定。我真的无法表达对我妻子的感激之情,她在本书的写作、出版和定期更新的整个过程中给予了我如此支持和耐心。我确实是一个非常幸运的人。
In every book’s acknowledgments section, the author invariably thanks family members and spouses, and there’s good reason for that. It takes a huge amount of understanding and sacrifice to tolerate your tiredness, your unavailability to do many of the things ordinary people get to do and your unreasonable decision to devote more time to a project than to them. I truly cannot express the depth of my gratitude to my wife for the way she has managed to be so supportive and patient throughout the process of getting this book written, published and regularly updated. I am indeed a very fortunate human being.
在至少了解其用途和使用方式的基础知识之前尝试使用任何工具很可能会导致挫败感。另一方面,花费所有时间学习某事物的理论而不进行实践会导致相当无聊的体验,并且常常会导致过于理想化的理解。本书的第一部分遵循 CMake 更基本的功能和概念的逻辑进展,其结构使读者能够立即进行实验并在每一章中做越来越有用的事情。目标是逐步建立有效使用 CMake 所需的基础知识,重点是能够立即将这些知识付诸实践。
Attempting to use any tool before understanding at least the basics of what it does and how it is meant to be used is most likely going to result in frustration. On the other hand, spending all one’s time learning the theory about something without getting hands-on makes for a rather boring experience and often leads to an overly idealistic understanding. This first part of the book follows a logical progression through CMake’s more fundamental features and concepts and is structured to enable the reader to immediately experiment and to do increasingly useful things with each chapter. The goal is to incrementally build up the base knowledge needed to use CMake effectively, with an emphasis on being able to put that knowledge into practice right away.
前几章的最初重点是构建基本的可执行文件或库,其内容足以让新开发人员快速了解 CMake。后续章节扩展了这些知识,以演示如何充分利用 CMake 所提供的功能。所提出的技术针对现实世界的使用,旨在建立良好的习惯并教授可扩展到非常大的项目并可以处理更复杂的场景的合理方法。
The initial focus in the first few chapters is on building a basic executable or library, covering just enough to give a new developer a quick introduction to CMake. Subsequent chapters expand that knowledge to demonstrate how to get the most out of what CMake has to offer. The techniques presented are aimed at real world use, with the intention of establishing good habits and teaching sound methods which scale to very large projects and can handle more complex scenarios.
本书的后面部分都很大程度上依赖于第一部分中涵盖的材料。那些已经使用 CMake 一段时间的人可能会发现这些主题相对熟悉,但该材料还包括来自现实世界项目以及与 CMake 社区互动来之不易的知识。即使是有经验的用户也应该至少发现每章末尾的推荐实践部分是有用的。
The later parts of the book all rely heavily on the material covered in this first part. Those who have already been using CMake for some time may find the topics relatively familiar, but the material also includes hard-won knowledge from real world projects and interaction with the CMake community. Even experienced users should find at least the Recommended Practices section at the end of each chapter to be a useful read.
无论是经验丰富的开发人员还是刚开始从事软件职业的人,都无法避免熟悉一系列工具的过程,以便将项目的源代码转变为最终用户可以实际使用的东西。编译器、链接器、测试框架、打包系统等都增加了部署高质量、健壮软件的复杂性。虽然某些平台具有主导的 IDE 环境,可以简化某些方面(例如 Xcode 和 Visual Studio),但需要支持多个平台的项目并不总是能够利用其功能。必须支持多个平台会增加更多的复杂性,这可能会影响从可用工具集到可用的不同功能和强制执行的限制的一切。一个典型的开发人员在试图掌握全局的过程中至少失去一些理智是可以原谅的。
Whether a seasoned developer or just starting out in a software career, one cannot avoid the process of becoming familiar with a range of tools in order to turn a project’s source code into something an end user can actually use. Compilers, linkers, testing frameworks, packaging systems and more all contribute to the complexity of deploying high quality, robust software. While some platforms have a dominant IDE environment that simplifies some aspects of this (e.g. Xcode and Visual Studio), projects that need to support multiple platforms cannot always make use of their features. Having to support multiple platforms adds more complications that can affect everything from the set of available tools through to the different capabilities available and restrictions enforced. A typical developer could be forgiven for losing at least some of their sanity trying to keep on top of the whole picture.
幸运的是,有一些工具可以使流程变得更易于管理。CMake 就是这样一个工具,或者更准确地说,CMake 是一套工具,涵盖从设置构建到生成可供分发的包的所有内容。它不仅涵盖了从开始到结束的整个过程,还支持广泛的平台、工具和语言。使用 CMake 时,有助于理解它的世界观。粗略地说,根据 CMake 的开始到结束过程如下所示:
Fortunately, there are tools that make taming the process more manageable. CMake is one such tool, or more accurately, CMake is a suite of tools which covers everything from setting up a build right through to producing packages ready for distribution. Not only does it cover the process from start to end, it also supports a wide range of platforms, tools and languages. When working with CMake, it helps to understand its view of the world. Loosely speaking, the start to end process according to CMake looks something like this:
第一阶段采用通用项目描述并生成适合与开发人员选择的常规构建工具(例如 make、Xcode、Visual Studio 等)一起使用的特定于平台的项目文件。虽然此设置阶段是 CMake 最为人所知的,但 CMake 工具套件还包括 CTest 和 CPack,分别用于管理后续测试和打包阶段。从开始到结束的整个过程都可以由 CMake 本身驱动,测试和打包步骤可以简单地作为构建中的附加目标。甚至构建工具也可以由 CMake 调用。
The first stage takes a generic project description and generates platform-specific project files suitable for use with the developer’s regular build tool of choice (e.g. make, Xcode, Visual Studio, etc.). While this setup stage is what CMake is best known for, the CMake suite of tools also includes CTest and CPack for managing the later testing and packaging stages respectively. The entire process from start to finish can be driven from CMake itself, with the testing and packaging steps available simply as additional targets in the build. Even the build tool can be invoked by CMake.
在开始使用 CMake 之前,开发人员首先需要确保他们的系统上安装了 CMake。某些平台通常可能已经安装了 CMake(例如,大多数 Linux 发行版都可以通过其包管理器使用 CMake),但这些版本通常相当旧。如果可能,建议开发人员使用最新的 CMake 版本。在为 Apple 平台进行开发时尤其如此,因为 Xcode 及其 SDK 等工具变化很快,而且应用程序商店的要求随着时间的推移而变化。可以下载官方 CMake 包并将其解压到开发人员计算机上的目录中,而不会干扰任何系统范围的 CMake 安装。我们鼓励开发人员利用这一点,并保持相对接近最新稳定的 CMake 版本。
Before jumping in and getting their hands dirty with CMake, developers will first need to ensure CMake is installed on their system. Some platforms may typically come with CMake already installed (eg most Linux distributions have CMake available through their package manager), but these versions are often quite old. Where possible, it is recommended that developers work with a recent CMake release. This is particularly true when developing for Apple platforms where tools like Xcode and its SDKs change rapidly and where app store requirements evolve over time. The official CMake packages can be downloaded and unpacked to a directory on the developer’s machine without interfering with any system-wide CMake install. Developers are encouraged to take advantage of this and remain relatively close to the most recent stable CMake release.
如今,CMake 还附带了相当广泛的 参考文档 ,可以从官方 CMake 网站访问。这个有用的资源对于查找各种命令、选项、关键字等非常有帮助,开发人员可能希望将其添加为书签以供快速参考。CMake论坛也是一个很好的建议来源,并且是在文档未提供足够指导的情况下询问 CMake 相关问题的推荐场所。
These days, CMake also comes with fairly extensive reference documentation which is accessible from the official CMake site. This useful resource is very helpful for looking up the various commands, options, keywords, etc. and developers will likely want to bookmark it for quick reference. The CMake forum is also a great source of advice and is the recommended place for asking CMake-related questions where the documentation doesn’t provide sufficient guidance.
如果没有构建系统,项目只是文件的集合。CMake 对此进行了一些排序,从一个名为的人类可读文件开始,
CMakeLists.txt该文件定义了应该构建什么、如何构建、要运行哪些测试以及要创建哪些包。该文件是整个项目的独立于平台的描述,然后 CMake 将其转换为特定于平台的构建工具项目文件。顾名思义,它只是一个普通的文本文件,开发人员可以在他们喜欢的文本编辑器或开发环境中编辑。该文件的内容将在后续章节中详细介绍,但就目前而言,只要知道这是控制 CMake 在设置和执行构建时将执行的所有操作的内容就足够了。
Without a build system, a project is just a collection of files. CMake
brings some order to this, starting with a human-readable file called
CMakeLists.txt that defines what should be built and how, what tests to
run and what package(s) to create. This file is a platform independent
description of the whole project, which CMake then turns into platform specific
build tool project files. As its name suggests, it is just an ordinary text
file which developers edit in their favorite text editor or development
environment. The contents of this file are covered in great detail in
subsequent chapters, but for now, it is enough to know that this is what
controls everything that CMake will do in setting up and performing the build.
CMake 的基本部分是具有源目录和二进制目录的项目的概念。源目录是
CMakeLists.txt文件所在的位置,项目的源文件和构建所需的所有其他文件都组织在该位置下。源目录经常使用 git、subversion 或类似工具进行版本控制。
A fundamental part of CMake is the concept of a project having both a source
directory and a binary directory. The source directory is where the
CMakeLists.txt file is located and the project’s source files and all other
files needed for the build are organized under that location. The source
directory is frequently under version control with a tool like git, subversion,
or similar.
二进制目录是创建构建生成的所有内容的地方。它通常也称为构建目录。出于在后面的章节中将变得清楚的原因,CMake 通常使用术语“二进制目录”,但在开发人员中,术语“构建目录”往往更常用。本书倾向于使用后一个术语,因为它通常更直观。CMake、所选的构建工具(例如make,Visual Studio 等)、CTest 和 CPack 都将在构建目录及其下面的子目录中创建各种文件。可执行文件、库、测试输出和包都在构建目录中创建。CMakeCache.txt
CMake 还会在构建目录中创建一个名为的特殊文件来存储各种信息,以便在后续运行中重用。开发人员通常不需要关心该
CMakeCache.txt文件,但后面的章节将讨论与该文件相关的情况。构建工具的项目文件(例如Xcode 或Visual Studio 项目文件、Makefile 等)也在构建目录中创建,并且不打算置于版本控制之下。这些CMakeLists.txt文件是项目的规范描述,生成的项目文件应被视为构建输出的一部分。
The binary directory is where everything produced by the build is created. It
is often also called the build directory. For reasons that will become clear in
later chapters, CMake generally uses the term binary directory, but among
developers, the term build directory tends to be in more common use. This book
tends to prefer the latter term since it is generally more intuitive. CMake,
the chosen build tool (e.g. make, Visual Studio, etc.), CTest and CPack will
all create various files within the build directory and subdirectories below
it. Executables, libraries, test output and packages are all created within the
build directory. CMake also creates a special file called CMakeCache.txt
in the build directory to store various information for reuse on subsequent
runs. Developers won’t normally need to concern themselves with the
CMakeCache.txt file, but later chapters will discuss situations where this
file is relevant. The build tool’s project files (e.g. Xcode or Visual Studio
project files, Makefiles, etc.) are also created in the build directory and are
not intended to be put under version control. The CMakeLists.txt files are
the canonical description of the project and the generated project files should
be considered part of the build output.
当开发人员开始项目工作时,他们必须决定构建目录相对于源目录的位置。本质上有两种方法:源内构建和源外构建。
When a developer commences work on a project, they must decide where they want their build directory to be in relation to their source directory. There are essentially two approaches: in-source and out-of-source builds.
尽管不鼓励,但源目录和构建目录可以相同。这种安排称为源内构建。开发人员在职业生涯初期通常会开始使用这种方法,因为这种方法被认为很简单。然而,源内构建的主要困难是所有构建输出都与源文件混合在一起。这种缺乏分离的情况会导致目录中充斥着各种文件和子目录,从而使管理项目源变得更加困难,并面临构建输出覆盖源文件的风险。它还使得使用版本控制系统变得更加困难,因为构建创建了许多文件,源代码控制工具必须知道要忽略这些文件,或者开发人员必须在提交期间手动排除这些文件。源内构建的另一个缺点是清除所有构建输出并使用干净的源代码树重新开始可能并不简单。由于这些原因,开发人员不鼓励尽可能使用源内构建,即使对于简单的项目也是如此。
It is possible, though discouraged, for the source and build directories to be the same. This arrangement is called an in-source build. Developers at the beginning of their career often start out using this approach because of the perceived simplicity. The main difficulty with in-source builds, however, is that all the build outputs are intermixed with the source files. This lack of separation causes directories to become cluttered with all sorts of files and subdirectories, making it harder to manage the project sources and running the risk of build outputs overwriting source files. It also makes working with version control systems more difficult, since there are lots of files created by the build which either the source control tool has to know to ignore or the developer has to manually exclude during commits. One other drawback to in-source builds is that it can be non-trivial to clear out all build output and start again with a clean source tree. For these reasons, developers are discouraged from using in-source builds where possible, even for simple projects.
更可取的安排是源目录和构建目录不同,这称为源外构建。这使源和构建输出彼此完全分离,从而避免了源内构建遇到的混合问题。源外构建还有一个优点,即开发人员可以为同一源目录创建多个构建目录,这允许使用不同的选项集设置构建,例如调试和发布版本等。
The more preferable arrangement is for the source and build directories to be different, which is called an out-of-source build. This keeps the sources and the build outputs completely separate from each other, thus avoiding the intermixing problems experienced with in-source builds. Out-of-source builds also have the advantage that the developer can create multiple build directories for the same source directory, which allows builds to be set up with different sets of options, such as debug and release versions, etc.
本书将始终使用源代码外构建,并将遵循源目录和构建目录位于共同父目录下的模式。构建目录将被称为build或其某种变体。例如:
This book will always use out-of-source builds and will follow the pattern of the source and build directories being under a common parent. The build directory will be called build, or some variation thereof. For example:
一些开发人员使用的一种变体是使构建目录成为源目录的子目录。这提供了源外构建的大部分优点,但它仍然带有源内安排的一些缺点。除非有充分的理由以这种方式构建事物,否则建议将构建目录完全保留在源代码树之外。
A variation on this used by some developers is to make the build directory a subdirectory of the source directory. This offers most of the advantages of an out-of-source build, but it does still carry with it some of the disadvantages of an in-source arrangement. Unless there is a good reason to structure things that way, keeping the build directory completely outside of the source tree instead is recommended.
选择目录结构后,开发人员运行 CMake,它读取文件CMakeLists.txt并在构建目录中创建项目文件。开发人员通过选择特定的项目文件生成器来选择要创建的项目文件的类型。支持一系列不同的生成器,下表列出了更常用的生成器。
Once the choice of directory structure has been made, the developer runs CMake,
which reads in the CMakeLists.txt file and creates project files in the build
directory. The developer selects the type of project file to be created by
choosing a particular project file generator. A range of different generators
are supported, with the more commonly used ones listed in the table below.
| 类别 | 生成器示例 | 多配置 |
|---|---|---|
视觉工作室 Visual Studio |
Visual Studio 16 2019 Visual Studio 16 2019 |
是的 Yes |
视觉工作室 15 2017 Visual Studio 15 2017 |
||
⋮ ⋮ |
||
Xcode Xcode |
Xcode Xcode |
是的 Yes |
忍者 Ninja |
忍者 Ninja |
不 No |
忍者多重配置 Ninja Multi-Config |
是的 Yes |
|
生成文件 Makefiles |
Unix Makefiles Unix Makefiles |
不 No |
MSYS 生成文件 MSYS Makefiles |
||
MinGW 生成文件 MinGW Makefiles |
||
NMake 生成文件 NMake Makefiles |
一些生成器生成支持多种配置的项目(例如调试、发布等)。这些允许开发人员在不同的构建配置之间进行选择,而无需重新运行 CMake,这更适合生成器创建在 Xcode 和 Visual Studio 等 IDE 环境中使用的项目。对于不支持多种配置的生成器,开发人员必须重新运行 CMake 以在调试、发布等之间切换构建。这些更简单,并且通常在与特定编译器(Qt Creator)关系不密切的 IDE 环境中具有良好的支持、KDevelop 等)。
Some of the generators produce projects which support multiple configurations (e.g. Debug, Release, etc.). These allow the developer to choose between different build configurations without having to re-run CMake, which is more suitable for generators creating projects for use in IDE environments like Xcode and Visual Studio. For generators which do not support multiple configurations, the developer has to re-run CMake to switch the build between Debug, Release, etc. These are simpler and often have good support in IDE environments not so closely associated with a particular compiler (Qt Creator, KDevelop, etc.).
运行 CMake 的最基本方法是通过cmake命令行实用程序。调用它的最简单方法是更改到构建目录并将选项传递给cmake生成器类型和源树的位置。例如:
The most basic way to run CMake is via the cmake command line utility. The
simplest way to invoke it is to change to the build directory and pass options
to cmake for the generator type and location of the source tree. For example:
mkdir 构建 光盘构建 cmake -G "Unix Makefiles" ../source
mkdir build cd build cmake -G "Unix Makefiles" ../source
如果-G省略该选项,CMake 将根据主机平台选择默认生成器类型。如果使用 CMake 3.15 或更高版本,可以通过将环境变量设置为所需的默认值来覆盖此默认值
CMAKE_GENERATOR。对于所有生成器类型,CMake 将执行一系列测试以确定如何设置项目文件。这包括验证编译器是否正常工作、确定支持的编译器功能集以及各种其他任务等。成功后,CMake 完成之前将记录各种信息,如下所示:
If the -G option is omitted, CMake will choose a default generator type
based on the host platform.
If using CMake 3.15 or later, this default can be overridden by setting the
CMAKE_GENERATOR environment variable to the desired default instead.
For all generator types, CMake will carry out a series of tests to determine
how to set up the project files.
This includes things like verifying that the compilers work, determining the
set of supported compiler features and various other tasks.
A variety of information will be logged before CMake finishes with lines like
the following upon success:
-- 配置完成 -- 生成完成 -- 构建文件已写入:/some/path/build
-- Configuring done -- Generating done -- Build files have been written to: /some/path/build
上面强调了项目文件的创建实际上涉及两个步骤;配置和生成。在配置阶段,CMake 读取文件
CMakeLists.txt并构建整个项目的内部表示。完成此操作后,生成阶段将创建项目文件。配置和生成之间的区别对于基本的 CMake 使用来说并不那么重要,但在后面的章节中,配置和生成的分离变得很重要。第 10 章“生成器表达式”对此进行了更详细的介绍
。
The above highlights that project file creation actually involves two steps;
configuring and generating. During the configuring phase, CMake reads in the
CMakeLists.txt file and builds up an internal representation of the entire
project. After this is done, the generation phase creates the project files.
The distinction between configuring and generating doesn’t matter so much for
basic CMake usage, but in later chapters the separation of configuration and
generation becomes important. This is covered in more detail in
Chapter 10, Generator Expressions.
当 CMake 完成运行时,它将CMakeCache.txt在构建目录中保存一个文件。CMake 使用此文件保存详细信息,以便再次运行时可以重用第一次计算的信息并加快项目生成速度。正如后面章节中所介绍的,它还允许在运行之间保存开发人员选项。GUI 应用程序cmake-gui可以作为运行cmake命令行工具的替代方法,但 GUI 应用程序的介绍推迟到第 5 章“变量” ,其中它的用处更加明显。
When CMake has completed its run, it will have saved a CMakeCache.txt file in
the build directory. CMake uses this file to save details so that if it is
run again, it can re-use information computed the first time and speed up
the project generation. As covered in later chapters, it also allows developer
options to be saved between runs. A GUI application, cmake-gui, is available
as an alternative to running the cmake command line tool, but the introduction
of the GUI application is deferred to Chapter 5, Variables where its usefulness is
more clearly evident.
此时,项目文件现已可用,开发人员可以按照他们习惯的方式使用他们选择的构建工具。构建目录将包含必要的项目文件,这些文件可以加载到 IDE 中、通过命令行工具读取等。或者,cmake可以代表开发人员调用构建工具,如下所示:
At this point, with project files now available, the developer can use their
selected build tool in the way to which they are accustomed. The build
directory will contain the necessary project files which can be loaded into an
IDE, read by command line tools, etc. Alternatively, cmake can invoke the
build tool on the developer’s behalf like so:
cmake --build /pathTo/build --config 调试 --target MyApp
cmake --build /pathTo/build --config Debug --target MyApp
即使对于开发人员可能更习惯通过 Xcode 或 Visual Studio 等 IDE 使用的项目类型,这也适用。该--build选项指向 CMake 项目生成步骤使用的构建目录。对于多配置生成器,该--config选项指定要构建的配置,而单配置生成器将忽略该--config
选项并依赖于执行 CMake 项目生成步骤时提供的信息。第 14 章“构建类型”中详细介绍了指定构建配置。该--target选项可用于告诉构建工具要构建什么,或者如果省略,将构建默认目标。使用 CMake 3.15 或更高版本,可以在选项后列出多个目标--target
,并用空格分隔。
This works even for project types the developer may be more accustomed to using
through an IDE like Xcode or Visual Studio. The --build option points to the
build directory used by the CMake project generation step. For multi
configuration generators, the --config option specifies which configuration
to build, whereas single configuration generators will ignore the --config
option and rely instead on information provided when the CMake project
generation step was performed. Specifying the build configuration is covered in
depth in Chapter 14, Build Type. The --target option can be used to tell the build
tool what to build, or if omitted, the default target will be built.
With CMake 3.15 or later, multiple targets can be listed after the --target
option, separated by spaces.
虽然开发人员通常会在日常开发中直接调用他们选择的构建工具,但通过cmake如上所示的命令调用它在驱动自动构建的脚本中可能更有用。使用这种方法,一个简单的脚本化构建可能看起来像这样:
While developers will typically invoke their selected build tool directly in
day-to-day development, invoking it via the cmake command as shown above can
be more useful in scripts driving an automated build. Using this approach, a
simple scripted build might look something like this:
mkdir 构建 光盘构建 cmake -G "Unix Makefiles" ../source cmake --build 。--config 发布 --target MyApp
mkdir build cd build cmake -G "Unix Makefiles" ../source cmake --build . --config Release --target MyApp
如果开发人员希望尝试不同的生成器,所需要做的就是更改-GCMake 选项的参数,然后将自动调用正确的构建工具。构建工具甚至不必在用户上即可PATH工作(尽管它可能需要在首次调用cmake --build时用于初始配置步骤)。cmake
If the developer wishes to experiment with different generators, all that
needs to be done is change the argument given to the -G CMake option and the
correct build tool will be automatically invoked. The build tool doesn’t even
have to be on the user’s PATH for cmake --build to work (although it may
need to be for the initial configuration step when cmake is first invoked).
即使第一次开始使用 CMake,也建议养成将构建目录与源代码树完全分开的习惯。尽早体验这种安排的好处的一个好方法是为同一源目录设置两个或多个不同的构建。一个构建可以配置调试设置,另一个构建可以配置发布构建。另一种选择是为不同的构建目录使用不同的项目生成器,例如 Unix Makefiles 和 Xcode。这可以帮助捕获对特定构建工具的任何意外依赖性,或检查生成器类型之间不同的编译器设置。
Even when first starting out using CMake, it is advisable to make a habit of keeping the build directory completely separate from the source tree. A good way to get early experience of the benefits of such an arrangement is to set up two or more different builds for the same source directory. One build could be configured with Debug settings, the other for a Release build. Another option is to use different project generators for the different build directories, such as Unix Makefiles and Xcode. This can help to catch any unintended dependencies on a particular build tool or to check for differing compiler settings between generator types.
在项目的早期阶段专注于使用一种特定类型的项目生成器可能很诱人,特别是如果开发人员不习惯编写跨平台软件的话。然而,项目确实有超出其初始范围的习惯,并且需要支持其他平台和生成器类型是相对常见的。定期使用与开发人员通常使用的项目生成器不同的项目生成器检查构建,可以通过阻止不需要的特定于生成器的代码来减少未来的痛苦。这还有一个好处是使该项目能够很好地利用未来任何新的发电机类型。一个好的策略是确保项目在每个感兴趣的平台上使用默认生成器类型以及其他类型进行构建。Ninja 生成器是后者的绝佳选择,因为它拥有所有生成器中最广泛的平台支持,并且还可以创建非常高效的构建。如果项目正在编写脚本,请通过调用构建工具,
cmake --build而不是直接调用构建工具。这使得脚本可以轻松地在生成器类型之间切换,而无需进行修改。
It can be tempting to focus on using one particular type of project generator
in the early stages of a project, especially if the developer is not accustomed
to writing cross-platform software. Projects do, however, have a habit of
growing beyond their initial scope and it is relatively common to
need to support additional platforms and generator types.
Periodically checking the build with a different project generator than the one
a developer usually uses can save considerable future pain by discouraging
generator-specific code where it isn’t required. This also has the benefit of
making the project well placed to take advantage of any new generator type in
the future. A good strategy is to ensure the project builds with the
default generator type on each platform of interest, plus one other type. The
Ninja generator is an excellent choice for the latter, since it has the
broadest platform support of all the generators and it also creates very
efficient builds. If the project is being scripted, invoke the build tool via
cmake --build instead of invoking the build tool directly. This allows the
script to easily switch between generator types without having to be modified.
所有 CMake 项目都以名为 的文件开始CMakeLists.txt,并且预计将其放置在源代码树的顶部。将其视为 CMake 项目文件,定义从源和目标到测试、打包和其他自定义任务的构建的所有内容。它可以简单到几行,也可以非常复杂并从其他目录中提取更多文件。
CMakeLists.txt只是一个普通的文本文件,通常可以直接编辑,就像项目中的任何其他源文件一样。
All CMake projects start with a file called CMakeLists.txt and it is expected
to be placed at the top of the source tree. Think of it as the CMake project
file, defining everything about the build from sources and targets through to
testing, packaging and other custom tasks. It can be as simple as a few lines
or it can be quite complex and pull in more files from other directories.
CMakeLists.txt is just an ordinary text file and is usually edited directly,
just like any other source file in the project.
继续与源类比,CMake 定义了自己的语言,其中有许多程序员熟悉的东西,例如变量、函数、宏、条件逻辑、循环、代码注释等。接下来的几章将介绍这些不同的概念和功能,但目前的目标只是以简单的构建作为起点。以下是CMakeLists.txt生成基本可执行文件的最小格式良好的文件。
Continuing the analogy with sources, CMake defines its own language which has
many things a programmer would be familiar with, such as variables, functions,
macros, conditional logic, looping, code comments and so on. These various
concepts and features are covered in the next few chapters, but for now, the
goal is just to get a simple build working as a starting point. The following
is a minimal, well-formed CMakeLists.txt file producing a basic executable.
cmake_minimum_required(VERSION 3.2)
project(MyApp)
add_executable(MyExe main.cpp)cmake_minimum_required(VERSION 3.2)
project(MyApp)
add_executable(MyExe main.cpp)
上面示例中的每一行都执行一个内置的 CMake命令。在 CMake 中,命令与其他语言的函数调用类似,不同之处在于虽然它们支持参数,但它们不直接返回值(但后面的章节将介绍如何以其他方式将值传递回调用者)。参数之间用空格分隔,并且可以分为多行:
Each line in the above example executes a built-in CMake command. In CMake, commands are similar to other languages’ function calls, except that while they support arguments, they do not return values directly (but a later chapter shows how to pass values back to the caller in other ways). Arguments are separated from each other by spaces and may be split across multiple lines:
add_executable(MyExe
main.cpp
src1.cpp
src2.cpp
)add_executable(MyExe
main.cpp
src1.cpp
src2.cpp
)
命令名称也不区分大小写,因此以下命令都是等效的:
Command names are also case insensitive, so the following are all equivalent:
add_executable(MyExe main.cpp)
ADD_EXECUTABLE(MyExe main.cpp)
Add_Executable(MyExe main.cpp)add_executable(MyExe main.cpp)
ADD_EXECUTABLE(MyExe main.cpp)
Add_Executable(MyExe main.cpp)
典型的风格各不相同,但目前更常见的约定是使用所有小写的命令名称(这也是内置命令的 CMake 文档遵循的约定)。
Typical style varies, but the more common convention these days is to use all lowercase for command names (this is also the convention followed by the CMake documentation for built-in commands).
CMake 不断更新和扩展,以添加对新工具、平台和功能的支持。CMake 背后的开发人员非常小心地维护与每个新版本的向后兼容性,因此当用户更新到较新版本的 CMake 时,项目应该像以前一样继续构建。有时,特定的 CMake 行为需要更改,或者在新版本中可能会引入更严格的检查和警告。CMake 并没有要求所有项目立即处理此问题,而是提供了允许项目“表现得像 CMake 版本 XYZ”的策略机制。这允许 CMake 在内部修复错误并引入新功能,但仍保持任何特定过去版本的预期行为。
CMake is continually updated and extended to add support for new tools, platforms and features. The developers behind CMake are very careful to maintain backwards compatibility with each new release, so when users update to a newer version of CMake, projects should continue to build as they did before. Sometimes, a particular CMake behavior needs to change or more stringent checks and warnings may be introduced in newer versions. Rather than requiring all projects to immediately deal with this, CMake provides policy mechanisms which allow the project to say “Behave like CMake version X.Y.Z”. This allows CMake to fix bugs internally and introduce new features, but still maintain the expected behavior of any particular past release.
项目指定有关其预期 CMake 版本行为的详细信息的主要方式是使用命令cmake_minimum_required()。这应该是文件的第一行,CMakeLists.txt以便在其他任何事情之前检查和确定项目的需求。该命令做了两件事:
The primary way a project specifies details about its expected CMake version
behavior is with the cmake_minimum_required() command. This should be the
first line of the CMakeLists.txt file so that the project’s requirements are
checked and established before anything else. This command does two things:
CMakeLists.txt早于指定版本的 CMake 版本处理该文件,它将立即停止并出现错误。这可确保在继续之前有一组特定的最小 CMake 功能可用。
CMakeLists.txt file is processed with a CMake version older than the one
specified, it will halt immediately with an error. This ensures that a
particular minimum set of CMake functionality is available before proceeding.
CMakeLists.txt使用此命令非常重要,如果该文件未在任何其他命令之前调用,CMake 将发出警告
cmake_minimum_required()。它需要知道如何为所有后续处理设置策略行为。cmake_minimum_required()
对于大多数项目,顾名思义,只需指定所需的最低 CMake 版本就足够了。事实上,它还意味着 CMake 的行为应该与特定版本相同,这可以被认为是一个有用的附带好处。第 12 章“策略”更详细地讨论了策略设置,并解释了如何根据需要调整此行为。
Using this command is so important that CMake will issue a warning if the
CMakeLists.txt file does not call cmake_minimum_required() before any other
command. It needs to know how to set up the policy behavior for all subsequent
processing. For most projects, it is enough to treat cmake_minimum_required()
as simply specifying the minimum required CMake version, as its name suggests.
The fact that it also implies CMake should behave the same as that particular
version can be considered a useful side benefit. Chapter 12, Policies discusses policy
settings in more detail and explains how to tailor this behavior as needed.
该cmake_minimum_required()命令的典型形式很简单:
The typical form of the cmake_minimum_required() command is straightforward:
cmake_minimum_required(VERSION major.minor[.patch[.tweak]])cmake_minimum_required(VERSION major.minor[.patch[.tweak]])
关键字VERSION必须始终存在,并且提供的版本详细信息必须至少包含该major.minor部分。在大多数项目中,指定
patch和tweak部分是不必要的,因为新功能通常只出现在次要版本更新中(这是从 3.0 版本开始的官方 CMake 行为)。仅当需要修复特定错误时,项目才应指定一个patch部分。此外,由于 3.x 系列中没有 CMake 版本使用过tweak数字,因此项目也不需要指定一个数字。
The VERSION keyword must always be present and the version details provided
must have at least the major.minor part. In most projects, specifying the
patch and tweak parts is not necessary, since new features typically only
appear in minor version updates (this is the official CMake behavior from
version 3.0 onward). Only if a specific bug fix is needed should a project
specify a patch part. Furthermore, since no CMake release in the 3.x series
has used a tweak number, projects should not need to specify one either.
开发人员应该仔细考虑他们的项目应该需要什么最低 CMake 版本。3.2 版本可能是任何新项目应该考虑的最旧的版本,因为它为现代 CMake 技术提供了相当完整的功能集。版本 2.8.12 的功能覆盖范围有所减少,缺乏许多有用的功能,但它可能适用于较旧的项目。之前的版本缺乏实质性功能,这使得许多现代 CMake 技术无法使用。如果使用快速移动的平台(例如 iOS),可能需要最新版本的 CMake 以支持最新的操作系统版本等。
Developers should think carefully about what minimum CMake version their project should require. Version 3.2 is perhaps the oldest any new project should consider, since it provides a reasonably complete feature set for modern CMake techniques. Version 2.8.12 has a reduced feature coverage, lacking a number of useful features but it may be workable for older projects. Versions before that lack substantial features that would make using many modern CMake techniques impossible. If working with fast-moving platforms such as iOS, quite recent versions of CMake may be needed in order to support the latest OS releases, etc.
作为一般经验法则,选择不会给项目构建人员带来重大问题的最新 CMake 版本。最大的困难通常是需要支持旧平台的项目,其中系统提供的 CMake 版本可能相当旧。对于这种情况,如果可能的话,开发人员应该考虑安装更新的版本,而不是限制自己使用非常旧的 CMake 版本。另一方面,如果该项目本身是其他项目的依赖项,那么选择更新的 CMake 版本可能会给采用带来障碍。在这种情况下,最好还是需要最旧的 CMake 版本,该版本仍提供所需的最低 CMake 功能,但如果可用,请使用较新 CMake 版本中的功能(第12 章,策略介绍了实现此目的的技术)。这将防止其他项目被迫需要比其目标环境通常允许或提供的更新的版本。如果依赖项目愿意,它们总是可以需要更新的版本,但不能需要较旧的版本。使用最旧的可用版本的主要缺点是,它可能会导致更多弃用警告,因为较新的 CMake 版本会警告较旧的行为,以鼓励项目自行更新。
As a general rule of thumb, choose the most recent CMake version that won’t present significant problems for those building the project. The greatest difficulty is typically experienced by projects that need to support older platforms where the system-provided version of CMake may be quite old. For such cases, if at all possible, developers should consider installing a more recent release rather than restricting themselves to very old CMake versions. On the other hand, if the project will itself be a dependency for other projects, then choosing a more recent CMake version may present a hurdle for adoption. In such cases, it may be beneficial to instead require the oldest CMake version that still provides the minimum CMake features needed, but make use of features from later CMake versions if available (Chapter 12, Policies presents techniques for achieving this). This will prevent other projects from being forced to require a more recent version than their target environment typically allows or provides. Dependent projects can always require a more recent version if they so wish, but they cannot require an older one. The main disadvantage of using the oldest workable version is that it may result in more deprecation warnings, since newer CMake versions will warn about older behaviors to encourage projects to update themselves.
每个 CMake 项目都应该包含一个命令,并且它应该在调用project()后出现。cmake_minimum_required()该命令及其最常见的选项具有以下形式:
Every CMake project should contain a project() command and it should
appear after cmake_minimum_required() has been called. The command with its
most common options has the following form:
project(projectName
[VERSION major[.minor[.patch[.tweak]]]]
[LANGUAGES languageName ...]
)project(projectName
[VERSION major[.minor[.patch[.tweak]]]]
[LANGUAGES languageName ...]
)
是projectName必需的,并且只能包含字母、数字、下划线 (_) 和连字符 (-),尽管在实践中通常只使用字母和下划线。由于不允许使用空格,因此项目名称不必用引号引起来。此名称用于带有某些项目生成器(例如 Xcode 和 Visual Studio)的项目的顶层,并且还用于项目的各个其他部分,例如充当打包和文档元数据的默认值、提供项目-特定变量等等。该名称是该命令的唯一强制参数
project()。
The projectName is required and may only contain letters, numbers,
underscores (_) and hyphens (-), although typically only letters and perhaps
underscores are used in practice. Since spaces are not permitted, the project
name does not have to be surrounded by quotes. This name is used for the top
level of a project with some project generators (eg Xcode and Visual Studio)
and it is also used in various other parts of the project, such as to act as
defaults for packaging and documentation metadata, to provide project-specific
variables and so on. The name is the only mandatory argument for the
project() command.
VERSION仅 CMake 3.0 及更高版本支持
可选详细信息。与 一样projectName,CMake 使用版本详细信息来填充一些变量并作为默认包元数据,但除此之外,版本详细信息没有任何其他意义。尽管如此,要养成的一个好习惯是在这里定义项目的版本,以便项目的其他部分可以引用它。第 20 章“指定版本详细信息”对此进行了深入介绍,并解释了如何在
CMakeLists.txt文件后面引用此版本信息。
The optional VERSION details are only supported in CMake 3.0 and later. Like
the projectName, the version details are used by CMake to populate some
variables and as default package metadata, but other than that, the version
details don’t have any other significance. Nonetheless, a good habit to
establish is to define the project’s version here so that other parts of the
project can refer to it. Chapter 20, Specifying Version Details covers this in depth
and explains how to refer to this version information later in the
CMakeLists.txt file.
可选LANGUAGES参数定义应为项目启用的编程语言。支持的值包括C、CXX、Fortran、
ASM、CUDA等。如果指定多种语言,请用空格分隔每种语言。在某些特殊情况下,项目可能希望表明不使用任何语言,这可以使用LANGUAGES NONE. 后面章节中介绍的技术利用了这种特殊的形式。如果未
LANGUAGES提供选项,CMake 将默认为C和CXX。3.0 之前的 CMake 版本不支持该LANGUAGES关键字,但仍然可以使用旧形式的命令在项目名称后指定语言,如下所示:
The optional LANGUAGES argument defines the programming languages that should
be enabled for the project. Supported values include C, CXX, Fortran,
ASM, CUDA and others. If specifying multiple languages, separate each with
a space. In some special situations, projects may want to indicate that no
languages are used, which can be done using LANGUAGES NONE. Techniques
introduced in later chapters take advantage of this particular form. If no
LANGUAGES option is provided, CMake will default to C and CXX. CMake
versions prior to 3.0 do not support the LANGUAGES keyword, but languages can
still be specified after the project name using the older form of the command
like so:
project(MyProj C CXX)project(MyProj C CXX)
鼓励新项目指定至少 3.0 的最低 CMake 版本,并使用带有LANGUAGES关键字的新形式。
New projects are encouraged to specify a minimum CMake version of at least 3.0
and use the new form with the LANGUAGES keyword instead.
该project()命令的作用不仅仅是填充几个变量。它的重要职责之一是检查每种启用语言的编译器,并确保它们能够成功编译和链接。编译器和链接器设置的问题很快就会被发现。一旦这些检查通过,CMake 就会设置许多变量和属性来控制启用语言的构建。
第 22 章“工具链和交叉编译”更详细地讨论了这一领域,包括影响工具链选择和配置的各种方法。
第 7 章“使用子目录”还讨论了影响命令使用的其他注意事项和要求project()。
The project() command does much more than just populate a few variables. One
of its important responsibilities is to check the compilers for each enabled
language and ensure they are able to compile and link successfully. Problems
with the compiler and linker setup are then caught very early. Once these
checks have passed, CMake sets up a number of variables and properties which
control the build for the enabled languages.
Chapter 22, Toolchains And Cross Compiling discusses this area in much greater detail,
including the various ways to influence toolchain selection and configuration.
Chapter 7, Using Subdirectories also discusses additional considerations and
requirements that affect the use of the project() command.
当 CMake 执行的编译器和链接器检查成功时,它们的结果将被缓存,以便在后续 CMake 运行中不必重复它们。这些缓存的详细信息存储在构建目录中的文件中
CMakeCache.txt。有关检查的其他详细信息可以在构建区域内的子目录中找到,但开发人员通常只需要在使用新的或不寻常的编译器或设置用于交叉编译的工具链文件时才需要查看那里。
When the compiler and linker checks performed by CMake are successful, their
results are cached so that they do not have to be repeated in subsequent CMake
runs. These cached details are stored in the build directory in the
CMakeCache.txt file. Additional details about the checks can be found in
subdirectories within the build area, but developers would typically only need
to look there if working with a new or unusual compiler or when setting up
toolchain files for cross-compiling.
为了完成我们的最小示例,该add_executable()命令告诉 CMake 从一组源文件创建一个可执行文件。该命令的基本形式是:
To complete our minimal example, the add_executable() command tells CMake
to create an executable from a set of source files. The basic form of this
command is:
add_executable(targetName source1 [source2 ...])add_executable(targetName source1 [source2 ...])
这将创建一个可执行文件,可以在 CMake 项目中将其称为
targetName. 该名称可能包含字母、数字、下划线和连字符。构建项目时,将在构建目录中创建一个可执行文件,其名称与平台相关,默认名称基于目标名称。考虑以下简单的示例命令:
This creates an executable which can be referred to within the CMake project as
targetName. This name may contain letters, numbers, underscores and hyphens.
When the project is built, an executable will be created in the build directory
with a platform-dependent name, the default name being based on the target
name. Consider the following simple example command:
add_executable(MyApp main.cpp)add_executable(MyApp main.cpp)
默认情况下,可执行文件的名称将MyApp.exe在 Windows 和
MyApp基于 Unix 的平台(如 macOS、Linux 等)上使用。可执行文件名称可以使用目标属性进行自定义,目标属性是第
9 章“属性”中介绍的 CMake 功能。CMakeLists.txt通过使用不同的目标名称多次调用,还可以在一个文件中定义多个可执行文件
add_executable()。如果在多个
add_executable()命令中使用相同的目标名称,CMake 将失败并突出显示错误。
By default, the name of the executable would be MyApp.exe on Windows and
MyApp on Unix-based platforms like macOS, Linux, etc. The executable name can
be customized with target properties, a CMake feature introduced in
Chapter 9, Properties. Multiple executables can also be defined within the one
CMakeLists.txt file by calling add_executable() multiple times with
different target names. If the same target name is used in more than one
add_executable() command, CMake will fail and highlight the error.
在离开本章之前,演示如何向CMakeLists.txt文件添加注释将很有用。本书中广泛使用了注释,并鼓励开发人员养成对项目进行注释的习惯,就像对普通源代码进行注释一样。CMake 遵循与 Unix shell 脚本类似的注释约定。任何以 # 字符开头的行都被视为注释。除了在带引号的字符串中之外,文件中行中 # 后面的任何内容CMakeLists.txt也被视为注释。下面显示了一些注释示例,并汇集了本章介绍的概念:
Before leaving this chapter, it will be useful to demonstrate how to add
comments to a CMakeLists.txt file. Comments are used extensively throughout
this book and developers are encouraged to also get into the habit of commenting
their projects just as they would for ordinary source code. CMake follows
similar commenting conventions as Unix shell scripts. Any line beginning with a
# character is treated as a comment. Except within a quoted string, anything
after a # on a line within a CMakeLists.txt file is also treated as a
comment. The following shows a few comment examples and brings together the
concepts introduced in this chapter:
cmake_minimum_required(VERSION 3.2)
# We don't use the C++ compiler, so don't let project()
# test for it in case the platform doesn't have one
project(MyApp VERSION 4.7.2 LANGUAGES C)
# Primary tool for this project
add_executable(MainTool
main.c
debug.c # Optimized away for release builds
)
# Helpful diagnostic tool for development and testing
add_executable(TestTool testTool.c)cmake_minimum_required(VERSION 3.2)
# We don't use the C++ compiler, so don't let project()
# test for it in case the platform doesn't have one
project(MyApp VERSION 4.7.2 LANGUAGES C)
# Primary tool for this project
add_executable(MainTool
main.c
debug.c # Optimized away for release builds
)
# Helpful diagnostic tool for development and testing
add_executable(TestTool testTool.c)
确保每个 CMake 项目都有一个cmake_minimum_required()命令作为其顶级CMakeLists.txt文件的第一行。在决定指定的最低所需版本号时,请记住版本越晚,项目能够使用的 CMake 功能就越多。这也意味着该项目可能会更好地适应新的平台或操作系统版本,这不可避免地会引入构建系统需要处理的新事物。相反,如果创建一个旨在作为操作系统本身的一部分进行构建和分发的项目(对于 Linux 来说很常见),则最低 CMake 版本可能由同一发行版提供的 CMake 版本决定。
Ensure every CMake project has a cmake_minimum_required() command as the
first line of its top level CMakeLists.txt file. When deciding the minimum
required version number to specify, keep in mind that the later the version,
the more CMake features the project will be able to use. It will also mean the
project is likely to be better placed to adapt to new platform or operating
system releases, which inevitably introduce new things for build systems to
deal with. Conversely, if creating a project intended to be built and
distributed as part of the operating system itself (common for Linux), the
minimum CMake version is likely to be dictated by the version of CMake provided
by that same distribution.
如果项目需要 CMake 3.0 或更高版本,那么尽早强制考虑项目版本号并project()尽快开始将版本号合并到命令中也是不错的选择。克服现有流程的惯性并改变项目后期处理版本号的方式可能非常困难。在决定版本控制策略时,请考虑流行的做法,例如
语义版本控制。
If the project can require CMake 3.0 or later, it is also good to force
thinking about project version numbers early and start incorporating version
numbering into the project() command as soon as possible. It can be very hard
to overcome the inertia of existing processes and change how version numbers
are handled later in the life of a project. Consider popular practices such as
Semantic Versioning when deciding on a versioning
strategy.
如上一章所示,在 CMake 中定义简单的可执行文件相对简单。前面给出的简单示例需要定义可执行文件的目标名称并列出要编译的源文件:
As shown in the previous chapter, it is relatively straightforward to define a simple executable in CMake. The simple example given previously required defining a target name for the executable and listing the source files to be compiled:
add_executable(MyApp main.cpp)add_executable(MyApp main.cpp)
这假设开发人员想要构建基本的控制台可执行文件,但 CMake 还允许开发人员定义其他类型的可执行文件,例如 Apple 平台上的应用程序包和 Windows GUI 应用程序。本章讨论可用于add_executable()指定这些详细信息的其他选项。
This assumes the developer wants a basic console executable to be built, but
CMake also allows the developer to define other types of executables, such as
app bundles on Apple platforms and Windows GUI applications. This chapter
discusses additional options which can be given to add_executable() to
specify these details.
除了可执行文件之外,开发人员还经常需要构建和链接库。CMake 支持几种不同类型的库,包括静态库、共享库、模块和框架。CMake 还提供了非常强大的功能来管理目标之间的依赖关系以及库的链接方式。整个库领域以及如何在 CMake 中使用它们构成了本章的大部分内容。这里涵盖的概念在本书的其余部分中广泛使用。还给出了变量和属性的一些非常基本的用法,以提供这些 CMake 功能如何与库和目标相关的一般风格。
In addition to executables, developers also frequently need to build and link libraries. CMake supports a few different kinds of libraries, including static, shared, modules and frameworks. CMake also offers very powerful features for managing dependencies between targets and how libraries are linked. This whole area of libraries and how to work with them in CMake forms the bulk of this chapter. The concepts covered here are used extensively throughout the remainder of this book. Some very basic use of variables and properties are also given to provide a flavor for how these CMake features relate to libraries and targets in general.
基本命令的更完整形式add_executable()如下:
The more complete form of the basic add_executable() command is as
follows:
add_executable(targetName [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)add_executable(targetName [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)
与之前显示的表单的唯一区别是新的可选关键字。
The only differences to the form shown previously are the new optional keywords.
WIN32
WIN32
WinMain()入口点创建,而不仅仅是入口点main(),并且它将与选项链接/SUBSYSTEM:WINDOWS。在所有其他平台上,该WIN32选项将被忽略。
WinMain() entry point instead
of just main() and it will be linked with the /SUBSYSTEM:WINDOWS option. On
all other platforms, the WIN32 option is ignored.
MACOSX_BUNDLE
MACOSX_BUNDLE
Info.plist捆绑包的基本文件。第 23.2 节“应用程序包”中更详细地介绍了这些细节和其他细节。在非 Apple 平台上,该MACOSX_BUNDLE关键字将被忽略。
Info.plist file for bundles. These and other details
are covered in more detail in Section 23.2, “Application Bundles”. On non-Apple platforms,
the MACOSX_BUNDLE keyword is ignored.
EXCLUDE_FROM_ALL
EXCLUDE_FROM_ALL
ALL将构建默认目标(根据所使用的 CMake 生成器,名称可能略有不同,例如ALL_BUILDXcode)。如果使用该选项定义了可执行文件EXCLUDE_FROM_ALL,则它将不会包含在该默认ALL目标中。只有当构建命令明确请求它或者它是默认构建一部分的另一个目标的依赖项时,才会构建可执行文件ALL。排除目标的常见情况ALL是,可执行文件是仅偶尔需要的开发人员工具。
ALL target is built (depending on the CMake generator being
used, the name may be slightly different, such as ALL_BUILD for Xcode). If an
executable is defined with the EXCLUDE_FROM_ALL option, it will not be
included in that default ALL target. The executable will then only be built
if it is explicitly requested by the build command or if it is a dependency for
another target that is part of the default ALL build. A common situation
where it can be useful to exclude a target from ALL is where the executable
is a developer tool that is only needed occasionally.
除了上述内容之外,还有其他形式的命令add_executable()
,它们生成对现有可执行文件或目标的一种引用,而不是定义要构建的新可执行文件或目标。这些别名可执行文件在第 17 章目标类型中有详细介绍。
In addition to the above, there are other forms of the add_executable()
command which produce a kind of reference to an existing executable or target
rather than defining a new one to be built. These alias executables are covered
in detail in Chapter 17, Target Types.
创建简单的可执行文件是任何构建系统的基本需求。对于许多大型项目来说,创建和使用库的能力对于保持项目的可管理性也至关重要。CMake 支持构建各种不同类型的库,考虑到许多平台差异,但仍然支持每个库的本机特性。库目标是使用add_library()命令定义的,该命令有多种形式。其中最基本的如下:
Creating simple executables is a fundamental need of any build system. For many
larger projects, the ability to create and work with libraries is also
essential to keep the project manageable. CMake supports building a variety of
different kinds of libraries, taking care of many of the platform differences,
but still supporting the native idiosyncrasies of each. Library targets are
defined using the add_library() command, of which there are a number of
forms. The most basic of these is the following:
add_library(targetName [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)add_library(targetName [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)
这种形式类似于如何add_executable()定义简单的可执行文件。targetName在文件中使用来CMakeLists.txt引用库,文件系统上构建的库的名称默认源自该名称。该EXCLUDE_FROM_ALL关键字与 的效果完全相同add_executable(),即防止库包含在默认ALL目标中。要构建的库的类型由其余三个关键字之一指定STATIC,SHARED
或MODULE。
This form is analogous to how add_executable() is used to define a simple
executable. The targetName is used within the CMakeLists.txt file to refer
to the library, with the name of the built library on the file system being
derived from this name by default. The EXCLUDE_FROM_ALL keyword has exactly
the same effect as it does for add_executable(), namely to prevent the
library from being included in the default ALL target. The type of library to
be built is specified by one of the remaining three keywords STATIC, SHARED
or MODULE.
STATIC
STATIC
targetName.lib,而在类 Unix 平台上,通常为libtargetName.a。
targetName.lib, while on Unix-like platforms, it would
typically be libtargetName.a.
SHARED
SHARED
targetName.dll,在 Apple 平台上为
libtargetName.dylib,在其他类 Unix 平台上通常为
libtargetName.so。在 Apple 平台上,共享库也可以标记为框架,该主题在第 23.3 节“框架”中介绍。
targetName.dll, on Apple platforms it would be
libtargetName.dylib and on other Unix-like platforms it would typically be
libtargetName.so. On Apple platforms, shared libraries can also be marked as
frameworks, a topic covered in Section 23.3, “Frameworks”.
MODULE
MODULE
可以省略定义要构建的库类型的关键字。除非项目特别需要特定类型的库,否则首选做法是不指定它,并将选择权留给开发人员在构建项目时。在这种情况下,库将是
STATIC或SHARED,选择由名为 的 CMake 变量的值决定BUILD_SHARED_LIBS。如果BUILD_SHARED_LIBS已设置为
true,则库目标将是共享库,否则它将是静态的。第 5 章变量中详细介绍了如何使用变量,但目前,设置此变量的一种方法是-D在命令行中包含一个选项,cmake
如下所示:
It is possible to omit the keyword defining what type of library to build.
Unless the project specifically requires a particular type of library, the
preferred practice is to not specify it and leave the choice up to the
developer when building the project. In such cases, the library will be either
STATIC or SHARED, with the choice determined by the value of a CMake
variable called BUILD_SHARED_LIBS. If BUILD_SHARED_LIBS has been set to
true, the library target will be a shared library, otherwise it will be
static. Working with variables is covered in detail in Chapter 5, Variables, but for
now, one way to set this variable is by including a -D option on the cmake
command line like so:
cmake -DBUILD_SHARED_LIBS=YES /路径/到/源
cmake -DBUILD_SHARED_LIBS=YES /path/to/source
它可以在CMakeLists.txt文件中设置,而不是在任何命令之前放置以下内容add_library(),但这将需要开发人员在想要更改它时对其进行修改(即,它会不太灵活):
It could be set in the CMakeLists.txt file instead with the following placed
before any add_library() commands, but that would then require developers to
modify it if they wanted to change it (i.e. it would be less flexible):
set(BUILD_SHARED_LIBS YES)set(BUILD_SHARED_LIBS YES)
就像可执行文件一样,库目标也可以定义为引用某些现有的二进制文件或目标,而不是由项目构建。还支持另一种类型的伪库,用于将目标文件收集在一起,而无需创建静态库。这些都在第 17 章目标类型中详细讨论。
Just as for executables, library targets can also be defined to refer to some existing binary or target rather than being built by the project. Another type of pseudo-library is also supported for collecting together object files without going as far as creating a static library. These are all discussed in detail in Chapter 17, Target Types.
在考虑构成项目的目标时,开发人员通常习惯于考虑库 A 需要库 B,因此 A 链接到 B。这是查看库处理的传统方式,其中一个库需要一个库的想法另一个则非常简单。然而,实际上,库之间可能存在几种不同类型的依赖关系:
When considering the targets that make up a project, developers are typically used to thinking in terms of library A needing library B, so A is linked to B. This is the traditional way of looking at library handling, where the idea of one library needing another is very simplistic. In reality, however, there are a few different types of dependency relationships that can exist between libraries:
PRIVATE
PRIVATE
PUBLIC
PUBLIC
INTERFACE
INTERFACE
INTERFACE,add_library()例如使用目标来表示仅头文件库的依赖项时(请参阅第 17.2.4 节“接口库”)。
INTERFACE form of add_library(), such as when using a target to
represent a header-only library’s dependencies (see Section 17.2.4, “Interface Libraries”).
CMake 通过其命令捕获了更丰富的依赖关系集
target_link_libraries(),而不仅仅是需要链接的简单想法。命令的一般形式为:
CMake captures this richer set of dependency relationships with its
target_link_libraries() command, not just the simplistic idea of needing to
link. The general form of the command is:
target_link_libraries(targetName
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)target_link_libraries(targetName
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)
这使得项目能够精确定义一个库如何依赖其他库。然后,CMake 负责管理以这种方式链接的整个库链中的依赖关系。例如,请考虑以下情况:
This allows projects to precisely define how one library depends on others. CMake then takes care of managing the dependencies throughout the chain of libraries linked in this fashion. For example, consider the following:
add_library(Collector src1.cpp)
add_library(Algo src2.cpp)
add_library(Engine src3.cpp)
add_library(Ui src4.cpp)
add_executable(MyApp main.cpp)
target_link_libraries(Collector
PUBLIC Ui
PRIVATE Algo Engine
)
target_link_libraries(MyApp PRIVATE Collector)add_library(Collector src1.cpp)
add_library(Algo src2.cpp)
add_library(Engine src3.cpp)
add_library(Ui src4.cpp)
add_executable(MyApp main.cpp)
target_link_libraries(Collector
PUBLIC Ui
PRIVATE Algo Engine
)
target_link_libraries(MyApp PRIVATE Collector)
在此示例中,Ui库作为 链接到Collector库
PUBLIC,因此即使仅MyApp直接链接到Collector,MyApp
也会Ui由于这种PUBLIC关系而链接到。另一方面, 和 库链接到as ,
因此不会直接链接到它们
Algo
。第 17.2 节“库”讨论了静态库的其他行为,这些行为可能导致进一步链接以满足依赖关系,包括循环依赖关系。EngineCollectorPRIVATEMyApp
In this example, the Ui library is linked to the Collector library as
PUBLIC, so even though MyApp only directly links to Collector, MyApp
will also be linked to Ui because of that PUBLIC relationship. The Algo
and Engine libraries, on the other hand, are linked to Collector as
PRIVATE, so MyApp will not be directly linked to them.
Section 17.2, “Libraries” discusses additional behaviors for static libraries
which may result in further linking to satisfy dependency relationships,
including cyclic dependencies.
后面的章节介绍了一些其他target_…()命令,这些命令进一步增强了目标之间携带的依赖性信息。这些允许编译器/链接器标志和标头搜索路径在通过 . 连接时也从一个目标传递到另一个目标target_link_libraries()。这些功能从 CMake 2.8.11 到 3.2 逐步添加,并导致CMakeLists.txt文件变得更加简单和强大。
Later chapters present a few other target_…() commands which further
enhance the dependency information carried between targets. These allow
compiler/linker flags and header search paths to also carry through from one
target to another when they are connected by target_link_libraries(). These
features were added progressively from CMake 2.8.11 through to 3.2 and lead to
considerably simpler and more robust CMakeLists.txt files.
后面的章节还讨论了更复杂的源目录层次结构的使用。在这种情况下,如果使用 CMake 3.12 或更早版本,则targetName
使用的 with必须由调用的同一目录中的
or命令target_link_libraries()定义
(此限制在 CMake 3.13 中已删除)。add_executable()add_library()target_link_libraries()
Later chapters also discuss the use of more complex source directory
hierarchies. In such cases, if using CMake 3.12 or earlier, the targetName
used with target_link_libraries() must have been defined by an
add_executable() or add_library() command in the same directory from which
target_link_libraries() is being called (this restriction was removed in
CMake 3.13).
在上一节中,链接到的所有项目都是现有的 CMake 目标,但该target_link_libraries()命令比这更灵活。除了 CMake 目标之外,还可以将以下内容指定为target_link_libraries()命令中的项目:
In the preceding section, all the items being linked to were existing CMake
targets, but the target_link_libraries() command is more flexible than that.
In addition to CMake targets, the following things can also be specified as
items in a target_link_libraries() command:
/usr/lib/libfoo.so)-lfoo。3.3 之前的行为的推理和细节非常重要,并且很大程度上是历史性的,但对于感兴趣的读者来说,完整的信息集可以在该CMP0060策略下的 CMake 文档中找到。
/usr/lib/libfoo.so with -lfoo). The reasoning and details of the pre-3.3
behavior are non-trivial and are largely historical, but for the interested
reader, the full set of information is available in the CMake documentation
under the CMP0060 policy.
foo变为-lfoo或
foo.lib,具体取决于平台)。这对于系统提供的库来说很常见。
foo becomes -lfoo or
foo.lib, depending on the platform). This would be common for libraries
provided by the system.
-l作为一种特殊情况,以除或
之外的连字符开头的项目-framework将被视为要添加到链接器命令中的标志。CMake 文档警告说,这些只能用于项目,因为如果定义为or
PRIVATE,它们将被传递到其他目标,这可能并不总是安全的。PUBLICINTERFACE
-l or
-framework will be treated as flags to be added to the linker command. The
CMake documentation warns that these should only be used for PRIVATE items,
since they would be carried through to other targets if defined as PUBLIC or
INTERFACE and this may not always be safe.
除上述内容外,由于历史原因,任何项目前面都可能带有关键字debug、optimized或 之一general。这些关键字的作用是根据构建是否配置为调试构建来进一步细化何时应包含其后面的项目(请参阅第
14 章,构建类型)。如果某个项目前面有debug关键字,则仅当构建是调试构建时才会添加它。如果某个项目前面有
optimized关键字,则仅当构建不是调试构建时才会添加该项目。关键字general指定应为所有构建配置添加该项目,如果不使用关键字,这无论如何都是默认行为。debug新项目应避免使用,optimized和关键字general,因为有更清晰、更灵活和更强大的方法可以使用当今的 CMake 功能实现相同的目标。
In addition to the above, for historical reasons, any item may be preceded by
one of the keywords debug, optimized or general. The effect of these
keywords is to further refine when the item following it should be included
based on whether or not the build is configured as a debug build (see
Chapter 14, Build Type). If an item is preceded by the debug keyword, then it will
only be added if the build is a debug build. If an item is preceded by the
optimized keyword, it will only be added if the build is not a debug build.
The general keyword specifies that the item should be added for all build
configurations, which is the default behavior anyway if no keyword is used.
The debug, optimized and general keywords should be avoided for new
projects as there are clearer, more flexible and more robust ways to achieve
the same thing with today’s CMake features.
该target_link_libraries()命令还有一些其他形式,其中一些形式早在 2.8.11 版本之前就已成为 CMake 的一部分。此处讨论这些形式是为了帮助理解旧的 CMake 项目,但通常不鼓励在新项目中使用它们。前面显示的完整形式PRIVATE,PUBLIC以及 和INTERFACE部分应该是首选,因为它更准确地表达了依赖关系的本质。
The target_link_libraries() command also has a few other forms, some of which
have been part of CMake from well before version 2.8.11. These forms are
discussed here for the benefit of understanding older CMake projects, but their
use is generally discouraged for new projects. The full form shown previously
with PRIVATE, PUBLIC and INTERFACE sections should be preferred, as it
expresses the nature of dependencies with more accuracy.
target_link_libraries(targetName item [item...])target_link_libraries(targetName item [item...])
上述形式通常等同于被定义为 的项目PUBLIC,但在某些情况下,它们可以被视为PRIVATE。特别是,如果项目定义了一个混合了新旧形式命令的库依赖项链,则旧式形式通常会被视为PRIVATE.
The above form is generally equivalent to the items being defined as PUBLIC,
but in certain situations, they may instead be treated as PRIVATE. In
particular, if a project defines a chain of library dependencies with a mix of
both old and new forms of the command, the old-style form will generally be
treated as PRIVATE.
另一种受支持但已弃用的形式如下:
Another supported but deprecated form is the following:
target_link_libraries(targetName
LINK_INTERFACE_LIBRARIES item [item...]
)target_link_libraries(targetName
LINK_INTERFACE_LIBRARIES item [item...]
)
这是INTERFACE上面介绍的较新形式的关键字的前体,但 CMake 文档不鼓励使用它。其行为可以影响不同的目标属性,并由策略设置控制该行为。这对开发人员来说是一个潜在的混乱来源,可以通过使用更新的INTERFACE形式来避免。
This is a pre-cursor to the INTERFACE keyword of the newer form covered
above, but its use is discouraged by the CMake documentation. Its behavior can
affect different target properties, with the policy settings controlling that
behavior. This is a potential source of confusion for developers which can be
avoided by using the newer INTERFACE form instead.
target_link_libraries(targetName
<LINK_PRIVATE|LINK_PUBLIC> lib [lib...]
[<LINK_PRIVATE|LINK_PUBLIC> lib [lib...]]
)target_link_libraries(targetName
<LINK_PRIVATE|LINK_PUBLIC> lib [lib...]
[<LINK_PRIVATE|LINK_PUBLIC> lib [lib...]]
)
与之前的旧式表单类似,此表单是
新表单的PRIVATE和关键字版本的前身。PUBLIC同样,旧式形式对于它影响哪些目标属性也存在同样的混乱,并且
PRIVATE/PUBLIC关键字形式应该是新项目的首选。
Similar to the previous old-style form, this one is a pre-cursor to the
PRIVATE and PUBLIC keyword versions of the newer form. Again, the old-style
form has the same confusion over which target properties it affects and the
PRIVATE/PUBLIC keyword form should be preferred for new projects.
目标名称不需要与项目名称相关。教程和示例使用变量作为项目名称并重用该变量作为可执行目标的名称是很常见的,如下所示:
Target names need not be related to the project name. It is common to see tutorials and examples use a variable for the project name and reuse that variable for the name of an executable target like so:
# Poor practice, but very common
set(projectName MyExample)
project(${projectName})
add_executable(${projectName} ...)# Poor practice, but very common
set(projectName MyExample)
project(${projectName})
add_executable(${projectName} ...)
这仅适用于最基本的项目,并会助长许多坏习惯。将项目名称和可执行文件名称视为独立的,即使它们最初是相同的。直接设置项目名称而不是通过变量,根据目标的用途而不是其所属的项目选择目标名称,并假设项目最终需要定义多个目标。这可以强化更好的习惯,这在处理更复杂的多目标项目时非常重要。
This only works for the most basic of projects and encourages a number of bad habits. Consider the project name and executable name as being separate, even if initially they start out the same. Set the project name directly rather than via a variable, choose a target name according to what the target does rather than the project it is part of and assume the project will eventually need to define more than one target. This reinforces better habits which will be important when working on more complex multi-target projects.
为库的目标命名时,请抵制以 . 开头或结尾的诱惑lib。在许多平台上(即除了 Windows 之外的几乎所有平台),lib在构建实际的库名称时将自动添加前导前缀,以使其符合平台的通常约定。如果目标名称已经以 开头lib,则生成的库文件名将以 的形式结尾liblibsomething….,人们通常认为这是一个错误。
When naming targets for libraries, resist the temptation to start or end the
name with lib. On many platforms (i.e. just about all except Windows), a
leading lib will be prefixed automatically when constructing the actual
library name to make it conform to the platform’s usual convention. If the
target name already begins with lib, the resultant library file names end up
with the form liblibsomething…., which people often assume to be a
mistake.
除非有充分的理由这样做,否则请尽量避免为库指定STATIC
orSHARED关键字,直到知道需要它为止。这使得在静态或动态库之间进行选择作为整个项目范围的策略具有更大的灵活性。该BUILD_SHARED_LIBS变量可用于在一个位置更改默认值,而不必修改每次调用
add_library().
Unless there are strong reasons to do so, try to avoid specifying the STATIC
or SHARED keyword for a library until it is known to be needed. This allows
greater flexibility in choosing between static or dynamic libraries as an
overall project-wide strategy. The BUILD_SHARED_LIBS variable can be used to
change the default in one place instead of having to modify every call to
add_library().
目标是在调用命令时始终指定PRIVATE,PUBLIC和/或INTERFACE关键字,target_link_libraries()而不是遵循旧式 CMake 语法(假设所有内容都是PUBLIC. 随着项目复杂性的增加,这三个关键字对如何处理目标间依赖关系产生更大的影响。从项目一开始就使用它们还迫使开发人员考虑目标之间的依赖关系,这有助于更早地突出项目中的结构问题。
Aim to always specify PRIVATE, PUBLIC and/or INTERFACE keywords when
calling the target_link_libraries() command rather than following the
old-style CMake syntax which assumed everything was PUBLIC. As a project
grows in complexity, these three keywords have a stronger impact on how
inter-target dependencies are handled. Using them from the beginning of a
project also forces developers to think about the dependencies between targets,
which can help to highlight structural problems within the project much
earlier.
前面的章节展示了如何定义基本目标并生成构建输出。就其本身而言,这已经很有用了,但 CMake 还附带了大量其他功能,带来了极大的灵活性和便利性。本章涵盖了 CMake 最基本的部分之一,即变量的使用。
The preceding chapters showed how to define basic targets and produce build outputs. On its own, this is already useful, but CMake comes with a whole host of other features which bring great flexibility and convenience. This chapter covers one of the most fundamental parts of CMake, namely the use of variables.
与任何计算语言一样,变量是 CMake 中完成任务的基石。定义变量的最基本方法是使用命令set()。普通变量可以在文件中定义CMakeLists.txt如下:
Like any computing language, variables are a cornerstone of getting things done
in CMake. The most basic way of defining a variable is with the set() command.
A normal variable can be defined in a CMakeLists.txt file as follows:
set(varName value... [PARENT_SCOPE])set(varName value... [PARENT_SCOPE])
变量名称varName可以包含字母、数字和下划线,字母区分大小写。名称中也可能包含字符./-+,但在实践中很少见到。其他字符也可以通过间接方式实现,但同样,这些字符在正常使用中通常不会出现。
The name of the variable, varName, can contain letters, numbers and
underscores, with letters being case-sensitive. The name may also contain the
characters ./-+ but these are rarely seen in practice. Other characters are
also possible via indirect means, but again, these are not typically seen in
normal use.
在 CMake 中,变量具有特定的作用域,就像其他语言中的变量的作用域仅限于特定的函数、文件等一样。变量不能在其作用域之外读取或修改。与其他语言相比,CMake 中的变量作用域更加灵活,但目前,在本章的简单示例中,将变量的作用域视为全局变量。
第 7 章“使用子目录”和第 8 章“函数和宏”介绍了出现局部作用域的情况,并展示了如何PARENT_SCOPE使用关键字来提高变量在封闭作用域中的可见性。
In CMake, a variable has a particular scope, much like how variables in other
languages have scope limited to a particular function, file, etc. A variable
cannot be read or modified outside of its scope. Compared to other languages,
variable scope is a little more flexible in CMake, but for now, in the simple
examples in this chapter, consider the scope of a variable as being global.
Chapter 7, Using Subdirectories and Chapter 8, Functions And Macros introduce the situations
where local scopes arise and show how the PARENT_SCOPE keyword is used to
promote the visibility of a variable into the enclosing scope.
CMake 将所有变量视为字符串。在各种上下文中,变量可能被解释为不同的类型,但最终它们只是字符串。设置变量的值时,CMake 不要求将这些值加引号,除非该值包含空格。如果给出多个值,这些值将连接在一起并用分号分隔每个值 - 生成的字符串是 CMake 表示列表的方式。以下内容应有助于演示该行为。
CMake treats all variables as strings. In various contexts, variables may be interpreted as a different type, but ultimately, they are just strings. When setting a variable’s value, CMake doesn’t require those values to be quoted unless the value contains spaces. If multiple values are given, the values will be joined together with a semicolon separating each value - the resultant string is how CMake represents lists. The following should help to demonstrate the behavior.
set(myVar a b c) # myVar = "a;b;c"
set(myVar a;b;c) # myVar = "a;b;c"
set(myVar "a b c") # myVar = "a b c"
set(myVar a b;c) # myVar = "a;b;c"
set(myVar a "b c") # myVar = "a;b c"set(myVar a b c) # myVar = "a;b;c"
set(myVar a;b;c) # myVar = "a;b;c"
set(myVar "a b c") # myVar = "a b c"
set(myVar a b;c) # myVar = "a;b;c"
set(myVar a "b c") # myVar = "a;b c"
变量的值是通过 获得的${myVar},它可以在任何需要字符串或变量的地方使用。CMake 特别灵活,因为还可以递归地使用此形式或指定要设置的另一个变量的名称。此外,CMake 不需要在使用变量之前定义它们。使用未定义的变量只会导致替换为空字符串,类似于 Unix shell 脚本的行为方式。默认情况下,使用未定义的变量不会发出警告,但
--warn-uninitialized可以向cmake命令提供该选项以启用此类警告。但请注意,这种使用很常见,并不一定是问题的症状,因此该选项的用途可能会受到限制。
The value of a variable is obtained with ${myVar}, which can be used anywhere
a string or variable is expected. CMake is particularly flexible in that it is
also possible to use this form recursively or to specify the name of another
variable to set. In addition, CMake doesn’t require variables to be defined
before using them. Use of an undefined variable simply results in an empty
string being substituted, similar to the way Unix shell scripts behave.
By default, no warning is issued for use of an undefined variable, but the
--warn-uninitialized option can be given to the cmake command to enable
such warnings. Be aware, however, that such use is very common and is not
necessarily a symptom of a problem, so the option’s usefulness may be limited.
set(foo ab) # foo = "ab"
set(bar ${foo}cd) # bar = "abcd"
set(baz ${foo} cd) # baz = "ab;cd"
set(myVar ba) # myVar = "ba"
set(big "${${myVar}r}ef") # big = "${bar}ef" = "abcdef"
set(${foo} xyz) # ab = "xyz"
set(bar ${notSetVar}) # bar = ""set(foo ab) # foo = "ab"
set(bar ${foo}cd) # bar = "abcd"
set(baz ${foo} cd) # baz = "ab;cd"
set(myVar ba) # myVar = "ba"
set(big "${${myVar}r}ef") # big = "${bar}ef" = "abcdef"
set(${foo} xyz) # ab = "xyz"
set(bar ${notSetVar}) # bar = ""
字符串不限于单行,它们可以包含嵌入的换行符。它们还可以包含引号,需要用反斜杠转义。
Strings are not restricted to being a single line, they can contain embedded newline characters. They can also contain quotes, which require escaping with backslashes.
set(myVar "goes here")
set(multiLine "First line ${myVar}
Second line with a \"quoted\" word")set(myVar "goes here")
set(multiLine "First line ${myVar}
Second line with a \"quoted\" word")
如果使用 CMake 3.0 或更高版本,引号的替代方法是使用受 lua 启发的括号语法,其中内容的开头由 标记[=[
,结尾由 标记]=]。方括号之间可以出现任意数量的=字符,包括根本不出现,但=开头和结尾必须使用相同数量的字符。如果左括号后紧跟一个换行符,则第一个换行符将被忽略,但后续的换行符不会被忽略。此外,不会对括号内的内容进行进一步的转换(即没有变量替换或转义)。
If using CMake 3.0 or later, an alternative to quotes is to use the
lua-inspired bracket syntax where the start of the content is marked by [=[
and the end with ]=]. Any number of = characters can appear between the
square brackets, including none at all, but the same number of = characters
must be used at the start and the end. If the opening brackets are immediately
followed by a newline character, that first newline is ignored, but subsequent
newlines are not. Furthermore, no further transformation of the bracketed
content is performed (i.e. no variable substitution or escaping).
# Simple multi-line content with bracket syntax,
# no = needed between the square bracket markers
set(multiLine [[
First line
Second line
]])
# Bracket syntax prevents unwanted substitution
set(shellScript [=[
#!/bin/bash
[[ -n "${USER}" ]] && echo "Have USER"
]=])
# Equivalent code without bracket syntax
set(shellScript
"#!/bin/bash
[[ -n \"\${USER}\" ]] && echo \"Have USER\"
")# Simple multi-line content with bracket syntax,
# no = needed between the square bracket markers
set(multiLine [[
First line
Second line
]])
# Bracket syntax prevents unwanted substitution
set(shellScript [=[
#!/bin/bash
[[ -n "${USER}" ]] && echo "Have USER"
]=])
# Equivalent code without bracket syntax
set(shellScript
"#!/bin/bash
[[ -n \"\${USER}\" ]] && echo \"Have USER\"
")
如上面的示例所示,括号语法特别适合定义 Unix shell 脚本等内容。此类内容将${…}语法用于其自身目的,并且经常包含引号,但使用括号语法意味着这些内容不必转义,这与定义 CMake 内容的传统引用样式不同。在和标记之间灵活使用任意数量的=
字符也意味着嵌入的方括号不会被误解为标记。第 19 章,使用文件包含更多示例,突出显示括号语法可以成为更好的替代方案的情况。[]
As the above example shows, bracket syntax is particularly well suited to
defining content like Unix shell scripts. Such content uses the ${…} syntax
for its own purpose and frequently contains quotes, but using bracket syntax
means these things do not have to be escaped, unlike the traditional quoting
style of defining CMake content. The flexibility to use any number of =
characters between the [ and ] markers also means embedded square brackets
do not get misinterpreted as markers. Chapter 19, Working With Files includes further
examples which highlight situations where bracket syntax can be a better
alternative.
可以通过调用unset()或set()
不带指定变量值的调用来取消设置变量。以下内容是等效的,如果myVar尚不存在,则不会出现错误或警告:
A variable can be unset either by calling unset() or by calling set()
with no value for the named variable. The following are equivalent, with no
error or warning if myVar does not already exist:
set(myVar)
unset(myVar)set(myVar)
unset(myVar)
除了项目定义供其自身使用的变量之外,许多 CMake 命令的行为还可能受到调用命令时特定变量的值的影响。这是 CMake 用于定制命令行为或修改默认值的常见模式,因此不必对每个命令、目标定义等重复它们。每个命令的 CMake 参考文档通常列出了可能影响该命令的任何变量。行为。本书的后续章节还重点介绍了许多有用的变量以及它们影响构建或提供有关构建的信息的方式。
In addition to variables defined by the project for its own use, the behavior of many of CMake’s commands can be influenced by the value of specific variables at the time the command is called. This is a common pattern used by CMake to tailor command behavior or to modify defaults so they don’t have to be repeated for every command, target definition, etc. The CMake reference documentation for each command typically lists any variables which can affect that command’s behavior. Later chapters of this book also highlight a number of useful variables and the way they affect or give information about the build.
CMake 还允许使用 CMake 变量表示法的修改形式来检索和设置环境变量的值。环境变量的值是使用特殊形式获得的$ENV{varName},并且可以在可以使用常规${varName}形式的任何地方使用。设置环境变量的方式与 CMake 变量类似,只不过使用而ENV{varName}不是仅varName作为要设置的变量。例如:
CMake also allows the value of environment variables to be retrieved and set
using a modified form of the CMake variable notation. The value of an
environment variable is obtained using the special form $ENV{varName} and
this can be used anywhere a regular ${varName} form can be used. Setting an
environment variable can be done in a similar way to a CMake variable,
except with ENV{varName} instead of just varName as the variable to set.
For example:
set(ENV{PATH} "$ENV{PATH}:/opt/myDir")set(ENV{PATH} "$ENV{PATH}:/opt/myDir")
但请注意,设置这样的环境变量只会影响当前运行的 CMake 实例。CMake 运行完成后,对环境变量的更改就会丢失。特别是,对环境变量的更改在构建时将不可见。因此,CMakeLists.txt像这样在文件中设置环境变量很少有用。
Note, however, that setting an environment variable like this only affects the
currently running CMake instance. As soon as the CMake run is finished, the
change to the environment variable is lost. In particular, the change to the
environment variable will not be visible at build time. Therefore, setting
environment variables within the CMakeLists.txt file like this is rarely
useful.
除了上面讨论的普通变量之外,CMake 还支持缓存
变量。与生命周期仅限于文件处理的普通变量不同CMakeLists.txt,缓存变量存储在CMakeCache.txt构建目录中调用的特殊文件中,并且它们在 CMake 运行之间持续存在。一旦设置,缓存变量将保持设置状态,直到某些内容明确地将它们从缓存中删除。缓存变量的值的检索方式与普通变量完全相同(即使用形式
${myVar}),但set()用于设置缓存变量时的命令不同:
In addition to normal variables discussed above, CMake also supports cache
variables. Unlike normal variables which have a lifetime limited to the
processing of the CMakeLists.txt file, cache variables are stored in the
special file called CMakeCache.txt in the build directory and they persist
between CMake runs. Once set, cache variables remain set until something
explicitly removes them from the cache. The value of a cache variable is
retrieved in exactly the same way as a normal variable (i.e. with the
${myVar} form), but the set() command is different when used to set a
cache variable:
set(varName value... CACHE type "docstring" [FORCE])set(varName value... CACHE type "docstring" [FORCE])
当CACHE存在关键字时,该set()命令将应用于指定的缓存变量varName而不是普通变量。缓存变量比普通变量附加了更多信息,包括标称类型和文档字符串。设置缓存变量时必须提供两者,尽管docstring
可以为空。文档字符串不会影响 CMake 处理变量的方式,它仅由 GUI 工具使用来提供帮助详细信息、工具提示等内容。
When the CACHE keyword is present, the set() command will apply to a cache
variable named varName instead of a normal variable.
Cache variables have more information attached to them than a normal variable,
including a nominal type and a documentation string.
Both must be provided when setting a cache variable, although the docstring
can be empty.
The documentation string does not affect how CMake treats the variable, it is
used only by GUI tools to provide things like help details, tooltips, etc.
CMake 在处理过程中始终将变量视为字符串。主要type用于改善 GUI 工具的用户体验,下一节将讨论一些值得注意的例外情况。必须type是以下之一:
CMake will always treat the variable as a string during processing.
The type is used mostly to improve the user experience in GUI tools, with
some notable exceptions discussed in the next section.
The type must be one of the following:
BOOL
BOOL
on/off值。GUI 工具使用复选框或类似的东西来表示变量。变量保存的底层字符串值将符合 CMake 将布尔值表示为字符串的方式之一(ON/ OFF、TRUE/ FALSE、1/0等 -
有关完整详细信息,请参阅第 6.1.1 节“基本表达式” )。
on/off value. GUI tools use a
checkbox or similar to represent the variable. The underlying string value held
by the variable will conform to one of the ways CMake represents booleans as
strings (ON/OFF, TRUE/FALSE, 1/0, etc. - see Section 6.1.1, “Basic Expressions”
for full details).
FILEPATH
FILEPATH
PATH
PATH
FILEPATH,但 GUI 工具提供一个选择目录而不是文件的对话框。
FILEPATH, but GUI tools present a dialog that selects a
directory rather than a file.
STRING
STRING
INTERNAL
INTERNAL
INTERNAL变量。
INTERNAL也意味着FORCE(见下文)。
INTERNAL variables.
INTERNAL also implies FORCE (see further below).
GUI 工具通常使用docstring作为缓存变量的工具提示或在选择变量时作为简短的一行描述。应该docstring
很短并且由纯文本组成(即没有 HTML 标记等)。
GUI tools typically use the docstring as a tooltip for the cache variable or
as a short one line description when the variable is selected. The docstring
should be short and consist of plain text (i.e. no HTML markup, etc.).
设置布尔缓存变量是一种常见的需求,CMake 为其提供了一个单独的命令。set()开发人员可以使用以下命令,
而不是使用有点冗长的命令option():
Setting a boolean cache variable is such a common need that CMake provides a
separate command for it.
Rather than the somewhat verbose set() command, developers can use
option() instead:
option(optVar helpString [initialValue])option(optVar helpString [initialValue])
如果initialValue省略,OFF将使用默认值。如果提供,则initialValue必须符合命令接受的布尔值之一set()。作为参考,上面的内容可以被认为或多或少等同于:
If initialValue is omitted, the default value OFF will be used. If
provided, the initialValue must conform to one of the boolean values accepted
by the set() command. For reference, the above can be thought of as more or
less equivalent to:
set(optVar initialValue CACHE BOOL helpString)set(optVar initialValue CACHE BOOL helpString)
与 相比set(),该option()命令更清楚地表达了布尔缓存变量的行为,因此它通常是首选使用的命令。但请注意,在某些情况下这两个命令的效果可能会有所不同(请参阅下一节)。
Compared to set(), the option() command more clearly expresses the
behavior for boolean cache variables, so it would generally be the preferred
command to use. Be aware, however, that the effect of the two commands can be
different in certain situations (see the next section).
普通变量和缓存变量之间的一个重要区别是,如果存在关键字,该set()
命令只会覆盖缓存变量,这与普通变量不同,命令将始终覆盖预先存在的值。当用于定义缓存变量时,该命令的行为更像是 set-if-not-set,命令也是如此(它没有
功能)。主要原因是缓存变量主要用作开发人员的自定义点。可以使用缓存变量,而不是将文件中的值硬编码为普通变量,以便开发人员可以覆盖该值,而无需编辑文件
。该变量可以通过交互式 GUI 工具或脚本进行修改,而无需更改项目本身的任何内容。FORCEset()set()option()FORCECMakeLists.txtCMakeLists.txt
An important difference between normal and cache variables is that the set()
command will only overwrite a cache variable if the FORCE keyword is present,
unlike normal variables where the set() command will always overwrite a
pre-existing value. The set() command acts more like set-if-not-set when used
to define cache variables, as does the option() command (which has no FORCE
capability). The main reason for this is that cache variables are primarily
intended as a customization point for developers. Rather than hard-coding the
value in the CMakeLists.txt file as a normal variable, a cache variable can be
used so that the developer can override the value without having to edit the
CMakeLists.txt file. The variable can be modified by interactive GUI tools or
by scripts without having to change anything in the project itself.
人们常常不太理解的一点是,普通变量和缓存变量是两个不同的东西。普通变量和缓存变量可以具有相同的名称,但保存不同的值。在这种情况下,CMake 在使用时将检索普通变量的值而不是缓存变量${myVar}。换句话说,普通变量优先于缓存变量。例外情况是,在设置缓存变量的值时,在以下情况下,任何同名的普通变量都会从当前作用域中删除(遵循下面进一步讨论的策略设置):
A point that is often not well understood is that normal and cache variables
are two separate things.
It is possible to have a normal variable and a cache variable with the same
name, but holding different values.
In such cases, CMake will retrieve the normal variable’s value rather than the
cache variable when using ${myVar}.
Put another way, normal variables take precedence over cache variables.
The exception to this is that when setting a cache variable’s value, any normal
variable of the same name is removed from the current scope in the following
situations (subject to policy settings discussed further below):
set()或之前,缓存变量不存在option()。
set() or option().
set()或之前就已存在option(),但它没有定义的类型(请参阅第 5.5.1 节“在命令行上设置缓存值”
了解如何发生这种情况)。
set() or option(), but it
did not have a defined type (see Section 5.5.1, “Setting Cache Values On The Command Line”
for how this can occur).
FORCEor选项。INTERNALset()
FORCE or INTERNAL option was used in the call to set().
在上面的前两种情况下,这意味着第一次和后续 CMake 运行之间可能会出现不同的行为。在第一次运行中,缓存变量将不存在或没有定义的类型,但在后续运行中它将存在。因此,在第一次运行中,普通变量将被隐藏,但在后续运行中,则不会。一个例子应该有助于说明问题:
In the first two cases above, this means it is possible to get different behavior between the first and subsequent CMake runs. In the first run, the cache variable won’t exist or won’t have a defined type, but in subsequent runs it will. Therefore, in the first run, a normal variable would be hidden, but in subsequent runs, it would not. An example should help illustrate the problem:
set(myVar foo) # Local myVar
set(result ${myVar}) # result = foo
set(myVar bar CACHE STRING "") # Cache myVar
set(result ${myVar}) # First run: result = bar
# Subsequent runs: result = foo
set(myVar fred)
set(result ${myVar}) # result = fredset(myVar foo) # Local myVar
set(result ${myVar}) # result = foo
set(myVar bar CACHE STRING "") # Cache myVar
set(result ${myVar}) # First run: result = bar
# Subsequent runs: result = foo
set(myVar fred)
set(result ${myVar}) # result = fred
第 7 章“使用子目录”和第 8 章“函数和宏”${myVar}进一步讨论了变量的作用域如何影响
返回的值。
Chapter 7, Using Subdirectories and Chapter 8, Functions And Macros contain further
discussions of how a variable’s scope can influence the value that ${myVar}
would return.
在 CMake 3.13 中, 的行为option()已更改,如果已存在同名的普通变量,则该命令不执行任何操作。这种更新的行为通常是开发人员直观地期望的。CMake 3.21 中的命令也进行了类似的更改set(),但请注意这两个命令的新行为存在以下差异:
In CMake 3.13, the behavior of option() was changed such that if a normal
variable already exists with the same name, the command does nothing.
This newer behavior is typically what developers intuitively expect.
A similar change was made for the set() command in CMake 3.21, but note the
following differences in the new behavior for both commands:
set(),如果缓存变量之前不存在,则仍然会设置该变量,但对于option(),则不存在。
set(), the cache variable is still set if it didn’t exist previously,
but for option() it is not.
INTERNAL或FORCE与 一起使用set(),则缓存变量将始终被设置或更新。
INTERNAL or FORCE is used with set(), the cache variable will always
be set or updated.
开发人员应注意这些不一致以及提供新行为的不同 CMake 版本。策略CMP0077并CMP0126控制实际行为(请参阅
第 12 章,策略以了解如何操纵这些行为)。
Developers should be mindful of these inconsistencies and the different CMake
versions that provide the new behaviors.
Policies CMP0077 and CMP0126 control the actual behavior (see
Chapter 12, Policies for an understanding of how these can be manipulated).
缓存和非缓存变量之间的交互也可能导致其他潜在的意外行为。考虑以下三个命令:
The interaction between cache and non-cache variables can also lead to other potentially unexpected behavior. Consider the following three commands:
unset(foo)
set(foo)
set(foo "")unset(foo)
set(foo)
set(foo "")
人们可能会想,${foo}在这三种情况中的任何一种之后,对 的求值总是会给出一个空字符串,但只有最后一种情况才能保证这样做。两者unset(foo)都set(foo)从当前作用域中删除非缓存变量。如果还有一个名为 的缓存变量foo,则该缓存变量将保持不变,并${foo}提供该缓存变量的值。从这个意义上说,unset(foo)和set(foo)两者都有效地取消了foo缓存变量的屏蔽(如果存在的话)。另一方面,set(foo "")
不会删除非缓存变量,它显式地将其设置为空值,因此${foo}无论是否还有名为 的缓存变量,都将始终评估为空字符串foo。因此,将变量设置为空字符串而不是删除它可能是实现开发人员意图的更可靠的方法。
One might be tempted to think that the evaluation of ${foo} would always give
an empty string after any of these three cases, but only the last is guaranteed
to do so. Both unset(foo) and set(foo) remove a non-cache variable from the
current scope. If there is also a cache variable called foo, that cache
variable is left alone and ${foo} would provide the value of that cache
variable. In this sense, unset(foo) and set(foo) both effectively unmask
the foo cache variable, if one exists. On the other hand, set(foo "")
doesn’t remove a non-cache variable, it explicitly sets it to an empty value,
so ${foo} will always then evaluate to an empty string regardless of whether
there is also a cache variable called foo. Therefore, setting a
variable to an empty string rather than removing it is likely to be the more
robust way of achieving the developer’s intention.
对于项目可能需要获取缓存变量的值并忽略任何同名非缓存变量的罕见情况,CMake 3.13 添加了表单文档$CACHE{someVar}。除了临时调试之外,项目通常不应使用此功能,因为它打破了长期以来的预期,即普通变量将覆盖缓存中设置的值。
For those rare situations where a project may need to get the value of a cache
variable and ignore any non-cache variable of the same name, CMake 3.13 added
documentation for the $CACHE{someVar} form. Projects should not generally
make use of this other than for temporary debugging, since it breaks with the
long-established expectation that normal variables will override values set in
the cache.
使用set()和option(),项目可以为其开发人员构建一组有用的自定义点。可以打开或关闭构建的不同部分,可以设置外部包的路径,可以修改编译器和链接器的标志等等。后面的章节将介绍缓存变量的这些和其他用途,但首先,需要了解操作这些变量的方法。开发人员可以通过两种主要方法来执行此操作,即通过cmake命令行或使用 GUI 工具。
Using set() and option(), a project can build up a useful set of
customization points for its developers. Different parts of the build can be
turned on or off, paths to external packages can be set, flags for compilers
and linkers can be modified and so on. Later chapters cover these and other
uses of cache variables, but first, the ways to manipulate such variables
need to be understood. There are two primary ways developers can do this,
either from the cmake command line or using a GUI tool.
CMake 允许通过传递给 的命令行选项直接操作缓存变量cmake。主要的主力是-D选项,它用于定义缓存变量的值。
CMake allows cache variables to be manipulated directly via command line
options passed to cmake. The primary workhorse is the -D option, which is
used to define the value of a cache variable.
cmake -D myVar:type=someValue ...
cmake -D myVar:type=someValue ...
someValue将替换myVar缓存变量的任何先前值。set()该行为本质上就像使用带有CACHE和选项的命令分配变量一样
FORCE。命令行选项只需要提供一次,因为它存储在缓存中以供后续运行,因此不需要每次cmake运行时都提供。可以提供多个-D选项来在命令行上一次设置多个变量cmake。
someValue will replace any previous value of the myVar cache variable. The
behavior is essentially as though the variable was being assigned using the
set() command with the CACHE and FORCE options. The command line option
only needs to be given once, since it is stored in the cache for subsequent
runs and therefore does not need to be provided every time cmake is run.
Multiple -D options can be provided to set more than one variable at a time
on the cmake command line.
当以这种方式定义缓存变量时,不必在文件中设置它们
CMakeLists.txt(即不需要相应的set()命令)。在命令行上定义的缓存变量有一个空的docstring. 也
type可以省略,在这种情况下,变量将具有未定义的类型,或者更准确地说,它被赋予一个类似于
INTERNAL但 CMake 解释为未定义的特殊类型。下面显示了通过命令行设置缓存变量的各种示例。
When defining cache variables this way, they do not have to be set within the
CMakeLists.txt file (i.e. no corresponding set() command is required).
Cache variables defined on the command line have an empty docstring. The
type can also be omitted, in which case the variable will have an undefined
type, or more accurately, it is given a special type that is similar to
INTERNAL but which CMake interprets to mean undefined. The following shows
various examples of setting cache variables via the command line.
cmake -D foo:BOOL=ON ... cmake -D "bar:STRING=这包含空格" ... cmake -D hideMe=mysteryValue ... cmake -D helpers:FILEPATH=subdir/helpers.txt ... cmake -D helpDir:PATH=/opt/helpThings ...
cmake -D foo:BOOL=ON ... cmake -D "bar:STRING=This contains spaces" ... cmake -D hideMe=mysteryValue ... cmake -D helpers:FILEPATH=subdir/helpers.txt ... cmake -D helpDir:PATH=/opt/helpThings ...
-D请注意,如果使用包含空格的值设置缓存变量,则应如何引用选项给定的整个值。
Note how the entire value given with the -D option should be quoted if
setting a cache variable with a value containing spaces.
有一种特殊情况,可以处理在cmake命令行上最初声明但没有类型的值。如果项目的CMakeLists.txt文件随后尝试设置相同的缓存变量并指定FILEPATH或的类型PATH,那么如果该缓存变量的值是相对路径,CMake 会将其视为相对于cmake调用的目录并自动转换它到绝对路径。这不是特别健壮,因为cmake可以从任何目录调用,而不仅仅是构建目录。cmake因此,如果在命令行上为表示某种路径的变量指定变量,建议开发人员始终包含类型。无论如何,总是在命令行上指定变量的类型是一个好习惯,这样它就可能以最合适的形式显示在 GUI 应用程序中。它还将防止前面第 5.4 节“变量的潜在令人惊讶的行为”中提到的场景之一
。
There is a special case for handling values initially declared without a type
on the cmake command line. If the project’s CMakeLists.txt file then tries
to set the same cache variable and specifies a type of FILEPATH or PATH,
then if the value of that cache variable is a relative path, CMake will treat
it as being relative to the directory from which cmake was invoked and
automatically convert it to an absolute path. This is not particularly robust,
since cmake could be invoked from any directory, not just the build
directory. Therefore, developers are advised to always include a type if
specifying a variable on the cmake command line for a variable that
represents some kind of path. It is a good habit to always specify the type of
the variable on the command line in general anyway so that it is likely to be
shown in GUI applications in the most appropriate form. It will also prevent
one of the scenarios mentioned earlier in
Section 5.4, “Potentially Surprising Behavior Of Variables”.
还可以使用该-U选项从缓存中删除变量,可以根据需要重复该选项以删除多个变量。请注意,该-U选项支持 * 和 ? 通配符,但需要注意避免删除超出预期的内容并使缓存处于不可构建的状态。一般来说,建议仅删除不带通配符的特定条目,除非绝对确定所使用的通配符是安全的。
It is also possible to remove variables from the cache with the -U option,
which can be repeated as necessary to remove more than one variable. Note that
the -U option supports * and ? wildcards, but care needs to be taken to avoid
deleting more than was intended and leaving the cache in an unbuildable state.
In general, it is recommended to only remove specific entries without wildcards
unless it is absolutely certain the wildcards used are safe.
cmake -U '帮助*' -U foo ...
cmake -U 'help*' -U foo ...
通过命令行设置缓存变量是自动构建脚本以及通过cmake命令驱动 CMake 的其他任何内容的重要组成部分。然而,对于日常开发来说,CMake 提供的 GUI 工具通常能够提供更好的用户体验。CMake 提供了两个等效的 GUI 工具cmake-gui
和ccmake,允许开发人员交互地操作缓存变量。cmake-gui是所有主要桌面平台都支持的功能齐全的 GUI 应用程序,而ccmake使用基于curses 的界面,可以在纯文本环境(例如通过连接)中使用ssh。
cmake-gui包含在所有平台的官方 CMake 发行包中,ccmake包含在除 Windows 之外的所有平台上。如果在 Linux 上使用系统提供的软件包而不是官方版本,请注意许多发行版会分成cmake-gui自己的软件包。
Setting cache variables via the command line is an essential part of automated
build scripts and anything else driving CMake via the cmake command. For
everyday development, however, the GUI tools provided by CMake often present a
better user experience. CMake provides two equivalent GUI tools, cmake-gui
and ccmake, which allow developers to manipulate cache variables
interactively. cmake-gui is a fully functional GUI application supported on
all major desktop platforms, whereas ccmake uses a curses-based interface
which can be used in text-only environments such as over a ssh connection.
cmake-gui is included in the official CMake release packages for all
platforms, ccmake is included for all platforms except Windows.
If using system-provided packages on Linux rather than the official releases,
note that many distributions split cmake-gui out into its own package.
用户cmake-gui界面如下图所示。顶部部分允许定义项目的源目录和构建目录。中间部分是可以查看和编辑缓存变量的地方。底部是Configure和Generate按钮,后面是显示这些操作的输出的日志区域。
The cmake-gui user interface is shown in the figure below. The top
section allows the project’s source and build directories to be defined.
The middle section is where the cache variables can be viewed and edited.
At the bottom are the Configure and Generate buttons, followed by a log
area that shows the output from those operations.
源目录必须设置为包含
CMakeLists.txt项目源树顶部的文件的目录。构建目录是 CMake 生成所有构建输出的位置(推荐的目录布局已在第 2 章“设置项目”中讨论)。对于新项目,两者都必须设置,但对于现有项目,设置构建目录也会更新源目录,因为源位置存储在构建目录的缓存中。
The source directory must be set to the directory containing the
CMakeLists.txt file at the top of the project’s source tree. The build
directory is where CMake will generate all build output (recommended directory
layouts were discussed in Chapter 2, Setting Up A Project). For new projects, both
must be set, but for existing projects, setting the build directory will also
update the source directory, since the source location is stored in the build
directory’s cache.
CMake 的两阶段设置过程在第 2.3 节“生成项目文件”中介绍。在第一阶段,CMakeLists.txt读取文件并在内存中构建项目的表示。这称为配置阶段。如果配置阶段成功,则可以执行生成阶段以在构建目录中创建构建工具的项目文件。cmake从命令行运行时
,两个阶段都会自动执行,但在 GUI 应用程序中,它们是通过Configure和
Generate按钮单独触发的。每次启动配置步骤时,UI 中间显示的缓存变量都会更新。任何新添加的变量或上次运行中更改值的变量都将以红色突出显示(首次加载项目时,所有变量都会突出显示)。良好的做法是重新运行配置阶段,直到没有任何更改,因为这可以确保更复杂的项目的稳健行为,在这些项目中,启用某些选项可能会添加更多选项,这可能需要另一个配置阶段。一旦所有缓存变量都显示且没有红色突出显示,则可以运行生成阶段。上一个屏幕截图中的示例显示了配置阶段运行后的典型日志输出,并且未对任何缓存变量进行任何更改。
CMake’s two-stage setup process was introduced in Section 2.3, “Generating Project Files”.
In the first stage, the CMakeLists.txt file is read and a representation of
the project is built up in memory. This is called the configure stage. If the
configure stage is successful, the generate stage can then be executed to
create the build tool’s project files in the build directory. When running
cmake from the command line, both stages are executed automatically, but in
the GUI application, they are triggered separately with the Configure and
Generate buttons. Each time the configure step is initiated, the cache
variables shown in the middle of the UI are updated. Any variables which were
newly added or which changed value from the previous run will be highlighted in
red (when a project is first loaded, all variables are shown highlighted). Good
practice is to re-run the configure stage until there are no changes, since
this ensures robust behavior for more complex projects where enabling some
options may add further options which could require another configure pass.
Once all cache variables are shown without red highlighting, the generate stage
can be run. The example in the previous screenshot shows typical log output
after the configure stage has been run and no changes were made to any of the
cache variables.
将鼠标悬停在任何缓存变量上将显示包含
docstring该变量的工具提示。新的缓存变量也可以使用按钮添加Add Entry,这相当于发出set()一个空的命令docstring。可以使用按钮删除缓存变量Remove Entry,尽管 CMake 很可能会在下次运行时重新创建该变量。
Hovering the mouse over any cache variable will show a tooltip containing the
docstring for that variable.
New cache variables can also be added with the Add Entry button, which is
equivalent to issuing a set() command with an empty docstring.
Cache variables can be removed with the Remove Entry button, although CMake
will most likely recreate that variable on the next run.
单击变量可以在针对变量类型定制的小部件中编辑其值。布尔值显示为复选框,文件和路径有一个浏览文件系统按钮,字符串通常显示为文本行编辑。作为一种特殊情况,可以为类型的缓存变量STRING指定一组值,以在 CMake GUI 的组合框中显示,而不是显示简单的文本输入小部件。这是通过设置缓存变量的属性来实现的(第 9.6 节“缓存变量属性”STRINGS中有详细介绍,但为了方便起见,在此处显示):
Clicking on a variable allows its value to be edited in a widget tailored to
the variable type. Booleans are shown as a checkbox, files and paths have a
browse filesystem button and strings are usually presented as a text line edit.
As a special case, cache variables of type STRING can be given a set of
values to show in a combobox in CMake GUI instead of showing a simple text
entry widget. This is achieved by setting a cache variable’s STRINGS property
(covered in detail in Section 9.6, “Cache Variable Properties”, but shown here for
convenience):
set(trafficLight Green CACHE STRING "Status of something")
set_property(CACHE trafficLight PROPERTY
STRINGS Red Orange Green
)set(trafficLight Green CACHE STRING "Status of something")
set_property(CACHE trafficLight PROPERTY
STRINGS Red Orange Green
)
在上面,trafficLight缓存变量最初的值为
Green。当用户尝试修改时trafficLight,cmake-gui他们将获得一个包含三个值的组合框Red,Orange而
Green不是一个简单的行编辑小部件,否则他们将允许他们输入任意文本。请注意,设置STRINGS变量的属性并不会阻止该变量分配其他值,它只会影响cmake-gui编辑它时使用的小部件。set()仍然可以通过文件中的命令
CMakeLists.txt或通过其他方式(例如手动编辑
文件)为变量指定其他值CMakeCache.txt。
In the above, the trafficLight cache variable will initially have the value
Green. When the user attempts to modify trafficLight in cmake-gui, they
will be given a combobox containing the three values Red, Orange and
Green instead of a simple line edit widget which would otherwise have allowed
them to enter any arbitrary text. Note that setting the STRINGS property on
the variable doesn’t prevent that variable from having other values assigned to
it, it only affects the widget used by cmake-gui when editing it. The
variable can still be given other values via set() commands in the
CMakeLists.txt file or by other means such as manually editing the
CMakeCache.txt file.
缓存变量还可以具有将它们标记为高级或非高级的属性。这也只影响变量在 中的显示方式cmake-gui,它不会以任何方式影响 CMake 在处理过程中如何使用变量。默认情况下,
cmake-gui仅显示非高级变量,通常仅显示开发人员有兴趣查看或修改的主要变量。启用该Advanced选项会显示除标记的变量之外的所有缓存变量
INTERNAL(查看变量的唯一方法INTERNAL是使用文本编辑器编辑
CMakeCache.txt文件,因为开发人员不打算直接操作它们)。mark_as_advanced()可以使用文件中的命令将变量标记为高级CMakeLists.txt:
Cache variables can also have a property marking them as advanced or not. This
too only affects the way the variable is displayed in cmake-gui, it does not
in any way affect how CMake uses the variable during processing. By default,
cmake-gui only shows non-advanced variables, which typically presents just
the main variables a developer would be interested in viewing or modifying.
Enabling the Advanced option shows all cache variables except those marked
INTERNAL (the only way to see INTERNAL variables is to edit the
CMakeCache.txt file with a text editor, since they are not intended to be
manipulated directly by developers). Variables can be marked as advanced with
the mark_as_advanced() command within the CMakeLists.txt file:
mark_as_advanced([CLEAR|FORCE] var1 [var2...])mark_as_advanced([CLEAR|FORCE] var1 [var2...])
关键字CLEAR确保变量不被标记为高级,而
FORCE关键字确保变量被标记为高级。如果没有任一关键字,则仅当变量尚未设置高级/非高级状态时,它们才会被标记为高级。
The CLEAR keyword ensures the variables are not marked as advanced, while the
FORCE keyword ensures the variables are marked advanced. Without either
keyword, the variables will only be marked as advanced if they don’t already
have an advanced/non-advanced state set.
选择该Grouped选项可以根据变量名称的开头到第一个下划线将变量分组在一起,从而更轻松地查看高级变量。过滤显示的变量列表的另一种方法是在该Search区域中输入文本,这会导致仅显示名称或值中包含指定文本的变量。
Selecting the Grouped option can make viewing advanced variables easier by
grouping variables together based on the start of the variable name up to the
first underscore. Another way to filter the list of variables shown is to enter
text in the Search area, which results in only showing variables with the
specified text in their name or value.
当配置阶段在新项目上首次运行时,开发人员会看到一个类似于下一个屏幕截图中所示的对话框:
When the configure stage is run for the first time on a new project, the developer is presented with a dialog similar to that shown in the next screenshot:
此对话框是指定 CMake 生成器和工具链的位置。生成器的选择通常取决于开发人员的个人喜好,组合框中提供了可用选项。根据项目的不同,生成器的选择可能比组合框选项允许的限制更严格,例如,如果项目依赖于生成器特定的功能。一个常见的例子是,由于 Apple 平台的独特功能(例如代码签名和 iOS/tvOS/watchOS 支持),需要 Xcode 生成器的项目。一旦为项目选择了生成器,就无法在不删除缓存并重新启动的情况下更改它,
File如果需要,可以从菜单中完成此操作。
This dialog is where the CMake generator and toolchain are specified. The
choice of generator is usually up to the developer’s personal preference, with
available options provided in the combobox. Depending on the project, the
choice of generator may be more restricted than what the combobox options
allow, such as if the project relies on generator-specific functionality. A
common example of this is a project that requires the Xcode generator due to
the Apple platform’s unique features, such as code signing and iOS/tvOS/watchOS
support. Once a generator has been selected for a project, it cannot be changed
without deleting the cache and starting again, which can be done from the
File menu if required.
对于所提供的工具链选项,每一种都需要开发人员提供越来越多的信息。使用默认的本机编译器是普通桌面开发的常见选择,选择该选项不需要更多细节。如果需要更多控制,开发人员可以覆盖本机编译器,并在后续对话框中给出编译器的路径。如果有单独的工具链文件可用,那么它不仅可以用于自定义编译器,还可以用于自定义目标环境、编译器标志和各种其他内容。交叉编译时通常会使用工具链文件, 第 22 章工具链和交叉编译对此进行了详细介绍。最后,为了最终控制,开发人员可以指定交叉编译的全套选项,但不建议在正常使用中这样做。工具链文件可以提供相同的信息,但具有可以根据需要重复使用的优点。
For the toolchain options presented, each one requires progressively more information from the developer. Using the default native compilers is the usual choice for ordinary desktop development and selecting that option requires no further details. If more control is required, developers can instead override the native compilers, with the paths to the compilers being given in a follow-up dialog. If a separate toolchain file is available, that can be used to customize not just the compilers but also the target environment, compiler flags and various other things. Using a toolchain file is typical when cross-compiling, which is covered in detail in Chapter 22, Toolchains And Cross Compiling. Lastly, for ultimate control, developers can specify the full set of options for cross-compiling, but this is not recommended for normal use. A toolchain file can provide the same information but has the advantage that it can be re-used as needed.
该ccmake工具提供了与应用程序大部分相同的功能cmake-gui
,但它是通过基于文本的界面来实现的:
The ccmake tool offers most of the same functionality as the cmake-gui
application, but it does so through a text-based interface:
不像使用那样选择源目录和构建目录
cmake-gui,而是必须在命令行上指定源目录或构建目录ccmake
,就像cmake命令一样。
Rather than selecting the source and build directories like with
cmake-gui, the source or build directory has to be specified on the ccmake
command line, just like for the cmake command.
该界面的一个小缺点ccmake是无法过滤显示的变量。编辑变量的方法也不如cmake-gui. 尽管如此,ccmake当完整的
cmake-gui应用程序不实用或不可用时(例如通过不支持 UI 转发的终端连接),该工具是一个有用的替代方案。
A minor drawback of the ccmake interface is that there is no ability to
filter the variables shown.
The methods for editing a variable are also not as rich as with cmake-gui.
Nevertheless, the ccmake tool is a useful alternative when the full
cmake-gui application is not practical or not available, such as over a
terminal connection that cannot support UI forwarding.
随着项目变得更加复杂或在调查意外行为时,在 CMake 运行期间打印出诊断消息和变量值可能会很有用。这通常使用命令来实现,第 13 章“调试和诊断”message()中详细介绍了该命令。现在,只要知道最简单的形式,该
命令所做的就是将其参数打印到 CMake 的输出中就足够了。如果给出多个参数,它不会在参数之间添加分隔符,并且换行符会自动附加到消息末尾。还可以使用通用符号显式包含换行符。可以使用通常的
符号将变量的值包含在消息中。message()\n${myVar}
As projects get more complicated or when investigating unexpected behavior, it
can be useful to print out diagnostic messages and variable values during a
CMake run.
This is generally achieved using the message() command, which is covered
in detail in Chapter 13, Debugging And Diagnostics.
For now, it is enough to know that in its simplest form, all the message()
command does is print its arguments to CMake’s output.
It adds no separator between arguments if more than one argument is given and a
newline is automatically appended to the end of the message.
Newlines can also be explicitly included using the common \n notation.
A variable’s value can be included in the message by using the usual ${myVar}
notation.
set(myVar HiThere)
message("The value of myVar = ${myVar}\nAnd this "
"appears on the next line")set(myVar HiThere)
message("The value of myVar = ${myVar}\nAnd this "
"appears on the next line")
这将给出以下输出:
This will give the following output:
myVar 的值 = HiThere 这出现在下一行
The value of myVar = HiThere And this appears on the next line
随着项目复杂性的增加,在许多情况下,需要实现更多复杂的变量管理逻辑。CMake 为此提供的一个核心工具是string()命令,它提供了广泛有用的字符串处理功能。该命令使项目能够执行查找和替换操作、正则表达式匹配、大小写转换、去除空格和其他常见任务。下面介绍了一些更常用的功能,但 CMake 参考文档应被视为所有可用操作及其行为的规范来源。
As project complexity grows, in many cases so too does the need to implement
more involved logic for how variables are managed. A core tool CMake provides
for this is the string() command, which provides a wide range of useful
string handling functionality. This command enables projects to perform find
and replace operations, regular expression matching, upper/lower case
transformations, strip whitespace and other common tasks. Some of the more
frequently used functionality is presented below, but the CMake reference
documentation should be considered the canonical source of all available
operations and their behavior.
第一个参数string()定义要执行的操作,后续参数取决于所请求的操作。这些参数通常需要至少一个输入字符串,并且由于 CMake 命令无法返回值,即操作结果的输出变量。在下面的材料中,该输出变量通常被命名为outVar。
The first argument to string() defines the operation to be performed and
subsequent arguments depend on the operation being requested. These arguments
will generally require at least one input string and since CMake commands
cannot return a value, an output variable for the result of the operation. In
the material below, this output variable will generally be named outVar.
string(FIND inputString subString outVar [REVERSE])string(FIND inputString subString outVar [REVERSE])
FIND搜索subStringin并存储找到的inputString索引(第一个字符是索引 0)。除非指定,否则将查找第一个出现的位置,在这种情况下,将查找最后一个出现的位置。如果中没有出现
,则将赋予值-1。subStringoutVarREVERSEsubStringinputStringoutVar
FIND searches for subString in inputString and stores the index of the
found subString in outVar (the first character is index 0). The first
occurrence is found unless REVERSE is specified, in which case the last
occurrence will be found instead. If subString does not appear in
inputString, then outVar will be given the value -1.
string(FIND abcdefabcdef def fwdIndex)
string(FIND abcdefabcdef def revIndex REVERSE)
message("fwdIndex = ${fwdIndex}\n"
"revIndex = ${revIndex}")string(FIND abcdefabcdef def fwdIndex)
string(FIND abcdefabcdef def revIndex REVERSE)
message("fwdIndex = ${fwdIndex}\n"
"revIndex = ${revIndex}")
这会产生以下输出:
This results in the following output:
前向索引 = 3 转速指数 = 9
fwdIndex = 3 revIndex = 9
替换简单子字符串遵循类似的模式:
Replacing a simple substring follows a similar pattern:
string(REPLACE matchString replaceWith outVar input...)string(REPLACE matchString replaceWith outVar input...)
该操作会将字符串中
REPLACE每次出现的 替换为 ,并将结果存储在 中。当给出多个字符串时,在搜索替换之前,它们将连接在一起,每个字符串之间不使用任何分隔符。这有时会导致意外的匹配,并且通常开发人员在大多数情况下只会提供一个字符串。matchStringinputreplaceWithoutVarinputinput
The REPLACE operation will replace every occurrence of matchString in the
input strings with replaceWith and store the result in outVar. When
multiple input strings are given, they are joined together without any
separator between each string before searching for substitutions. This can
sometimes lead to unexpected matches and typically developers would provide
just the one input string in most situations.
该操作也很好地支持正则表达式REGEX,根据第二个参数确定有一些不同的可用变体:
Regular expressions are also well supported by the REGEX operation, with a few
different variants available as determined by the second argument:
string(REGEX MATCH regex outVar input...)
string(REGEX MATCHALL regex outVar input...)
string(REGEX REPLACE regex replaceWith outVar input...)string(REGEX MATCH regex outVar input...)
string(REGEX MATCHALL regex outVar input...)
string(REGEX REPLACE regex replaceWith outVar input...)
匹配的正则表达式 ,regex可以使用典型的基本正则表达式语法(有关完整规范,请参阅 CMake 参考文档),但不支持某些常见功能(例如否定)。字符串input在替换之前连接。该
MATCH操作仅找到第一个匹配项并将其存储在outVar.
MATCHALL查找所有匹配项并将它们存储为outVar列表。REPLACE
将返回整个input字符串,每个匹配项都替换为
replaceWith。可以replaceWith使用常用符号 \1、\2 等来引用匹配项,但请注意,除非使用括号符号,否则反斜杠本身必须转义。以下示例及其输出演示了上述几点:
The regular expression to match, regex, can make use of typical basic regular
expression syntax (see the CMake reference documentation for the full
specification), although some common features such as negation are not
supported. The input strings are concatenated before substitution. The
MATCH operation finds just the first match and stores it in outVar.
MATCHALL finds all matches and stores them in outVar as a list. REPLACE
will return the entire input string with each match replaced by
replaceWith. Matches can be referred to in replaceWith using the usual
notation \1, \2, etc., but note that the backslashes themselves must be escaped
unless bracket notation is used.
The following example and its output demonstrate the above points:
string(REGEX MATCH "[ace]"
matchOne abcdefabcdef
)
string(REGEX MATCHALL "[ace]"
matchAll abcdefabcdef
)
string(REGEX REPLACE "([de])" "X\\1Y"
replVar1 abc def abcdef
)
string(REGEX REPLACE "([de])" [[X\1Y]]
replVar2 abcdefabcdef
)
message("matchOne = ${matchOne}\n"
"matchAll = ${matchAll}\n"
"replVar1 = ${replVar1}\n"
"replVar2 = ${replVar2}")string(REGEX MATCH "[ace]"
matchOne abcdefabcdef
)
string(REGEX MATCHALL "[ace]"
matchAll abcdefabcdef
)
string(REGEX REPLACE "([de])" "X\\1Y"
replVar1 abc def abcdef
)
string(REGEX REPLACE "([de])" [[X\1Y]]
replVar2 abcdefabcdef
)
message("matchOne = ${matchOne}\n"
"matchAll = ${matchAll}\n"
"replVar1 = ${replVar1}\n"
"replVar2 = ${replVar2}")
匹配一个 = a 全部匹配 = a;c;e;a;c;e replVar1 = abcXdYXeYfabcXdYXeYf replVar2 = abcXdYXeYfabcXdYXeYf
matchOne = a matchAll = a;c;e;a;c;e replVar1 = abcXdYXeYfabcXdYXeYf replVar2 = abcXdYXeYfabcXdYXeYf
提取子字符串也是可能的:
Extracting a substring is also possible:
string(SUBSTRING input index length outVar)string(SUBSTRING input index length outVar)
是index一个整数,定义要从中提取的子字符串的开头
input。length将提取最多字符,或者如果length为-1,则返回的子字符串将包含直到字符串末尾的所有字符input
。length
请注意,在 CMake 3.1 及更早版本中,如果指向字符串末尾,则会报告错误。
The index is an integer defining the start of the substring to extract from
input. Up to length characters will be extracted, or if length is -1, the
returned substring will contain all characters up to the end of the input
string. Note that in CMake 3.1 and earlier, an error was reported if length
pointed past the end of the string.
可以轻松获得字符串长度,并且可以轻松地将字符串转换为大写或小写。从字符串的开头和结尾去除空格也很简单。这些操作的语法都具有相同的形式:
String length can be trivially obtained and strings can easily be converted to upper or lower case. It is also straightforward to strip whitespace from the start and end of a string. The syntax for these operations all share the same form:
string(LENGTH input outVar)
string(TOLOWER input outVar)
string(TOUPPER input outVar)
string(STRIP input outVar)string(LENGTH input outVar)
string(TOLOWER input outVar)
string(TOUPPER input outVar)
string(STRIP input outVar)
在 的情况下LENGTH,由于历史原因,该命令计算字节而不是字符。对于包含多字节字符的字符串,这意味着报告的长度将与字符数不同。
In the case of LENGTH, for historical reasons the command counts bytes rather
than characters.
For strings containing multi-byte characters, this means the reported length
will be different to the number of characters.
CMake 提供其他操作,例如字符串比较、散列、时间戳、JSON 处理等,但它们在日常 CMake 项目中使用不太常见。感兴趣的读者应查阅该
string()命令的 CMake 参考文档以了解完整的详细信息。
CMake provides other operations, such as string comparison, hashing,
timestamps, JSON handling and more, but their use is less common in everyday
CMake projects.
The interested reader should consult the CMake reference documentation for the
string() command for full details.
列表在 CMake 中大量使用。最终,列表只是一个由分号分隔的列表项的字符串(有一个例外,该情况将在
下面的第5.8.1 节“不平衡方括号的问题”中介绍)。这会使操作单个列表项变得不太方便。CMake 提供了list()命令来促进此类任务。与string()命令一样,list()期望操作作为其第一个参数执行。a;b;c第二个参数始终是要操作的列表,并且它必须是变量(即不允许传递原始列表)。
Lists are used heavily in CMake.
Ultimately, lists are just a single string with list items separated by
semicolons (with one exception, which is covered in
Section 5.8.1, “Problems With Unbalanced Square Brackets” further below).
This can make it less convenient to manipulate individual list items.
CMake provides the list() command to facilitate such tasks.
Like for the string() command, list() expects the operation to perform as
its first argument.
The second argument is always the list to operate on and it must be a variable
(i.e. passing a raw list like a;b;c is not permitted).
最基本的列表操作是计算项目数并从列表中检索一个或多个项目:
The most basic list operations are counting the number of items and retrieving one or more items from the list:
list(LENGTH listVar outVar)
list(GET listVar index [index...] outVar)list(LENGTH listVar outVar)
list(GET listVar index [index...] outVar)
用法示例:
Example usage:
set(myList a b c) # Creates the list "a;b;c"
list(LENGTH myList len)
message("length = ${len}")
list(GET myList 2 1 letters)
message("letters = ${letters}")set(myList a b c) # Creates the list "a;b;c"
list(LENGTH myList len)
message("length = ${len}")
list(GET myList 2 1 letters)
message("letters = ${letters}")
上面示例的输出将是:
The output of the above example would be:
长度=3 字母=c;b
length = 3 letters = c;b
插入、追加和前置项目也是一项常见任务:
Inserting, appending and prepending items is also a common task:
list(INSERT listVar index item [item...])
list(APPEND listVar item [item...])
# Requires CMake 3.15 or later
list(PREPEND listVar item [item...])list(INSERT listVar index item [item...])
list(APPEND listVar item [item...])
# Requires CMake 3.15 or later
list(PREPEND listVar item [item...])
LENGTH与和 的GET情况不同, INSERT、APPEND和PREPEND直接作用于listVar并就地修改它,如以下示例所示:
Unlike the LENGTH and GET cases, INSERT, APPEND and PREPEND act
directly on the listVar and modify it in-place, as demonstrated by the
following example:
set(myList a b c)
list(INSERT myList 2 X Y Z)
message("myList (first) = ${myList}")
list(APPEND myList d e f)
message("myList (second) = ${myList}")
list(PREPEND myList P Q R)
message("myList (third) = ${myList}")set(myList a b c)
list(INSERT myList 2 X Y Z)
message("myList (first) = ${myList}")
list(APPEND myList d e f)
message("myList (second) = ${myList}")
list(PREPEND myList P Q R)
message("myList (third) = ${myList}")
给出以下输出:
Which gives the following output:
myList(第一个)= a;b;X;Y;Z;c myList(第二个)= a;b;X;Y;Z;c;d;e;f myList(第三个)= P;Q;R;a;b;X;Y;Z;c;d;e;f
myList (first) = a;b;X;Y;Z;c myList (second) = a;b;X;Y;Z;c;d;e;f myList (third) = P;Q;R;a;b;X;Y;Z;c;d;e;f
在列表中查找特定项目遵循预期模式:
Finding a particular item in the list follows the expected pattern:
list(FIND myList value outVar)list(FIND myList value outVar)
用法示例:
Example usage:
set(myList a b c d e)
list(FIND myList d index)
message("index = ${index}")set(myList a b c d e)
list(FIND myList d index)
message("index = ${index}")
结果输出:
Resultant output:
索引 = 3
index = 3
提供了三种删除项目的操作,所有这些操作都直接修改列表:
Three operations are provided for removing items, all of which modify the list directly:
list(REMOVE_ITEM myList value [value...])
list(REMOVE_AT myList index [index...])
list(REMOVE_DUPLICATES myList)list(REMOVE_ITEM myList value [value...])
list(REMOVE_AT myList index [index...])
list(REMOVE_DUPLICATES myList)
该REMOVE_ITEM操作可用于从列表中删除一项或多项的所有实例。如果该项目不在列表中,则不是错误。
REMOVE_AT另一方面,指定要删除的一个或多个索引,如果任何指定索引超出列表末尾,CMake 将停止并出现错误。
REMOVE_DUPLICATES将确保列表仅包含唯一的项目。
The REMOVE_ITEM operation can be used to remove all instances of one or more
items from a list.
If the item is not in the list, it is not an error.
REMOVE_AT on the other hand, specifies one or more indices to remove and
CMake will halt with an error if any of the specified indices are past the end
of the list.
REMOVE_DUPLICATES will ensure the list contains only unique items.
CMake 3.15 添加了对从列表前面或后面弹出项目并可选择存储弹出项目的支持:
CMake 3.15 added support for popping items from the front or back of a list and optionally storing the popped items:
# Requires CMake 3.15 or later
list(POP_FRONT myList [outVar1 [outVar2...]])
list(POP_BACK myList [outVar1 [outVar2...]])# Requires CMake 3.15 or later
list(POP_FRONT myList [outVar1 [outVar2...]])
list(POP_BACK myList [outVar1 [outVar2...]])
当没有outVar给出时,单个项目将从前面或后面弹出并丢弃。如果outVar给出一个或多个名称,弹出的项目将存储在这些变量中,弹出的项目数等于提供的变量名称的数量。
When no outVar is given, a single item is popped from the front or back and
discarded.
If one or more outVar names are given, popped items will be stored in those
variables, with the number of items popped equal to the number of variable
names provided.
列表项也可以使用REVERSE或SORT操作重新排序:
List items can also be reordered with REVERSE or SORT operations:
list(REVERSE myList)
list(SORT myList
[COMPARE method]
[CASE case]
[ORDER order]
)list(REVERSE myList)
list(SORT myList
[COMPARE method]
[CASE case]
[ORDER order]
)
所有可选关键字list(SORT)仅适用于 CMake 3.13 或更高版本。如果COMPARE存在该选项,则method必须是以下之一:
All of the optional keywords for list(SORT) are only available with CMake
3.13 or later.
If the COMPARE option is present, the method must be one of the following:
STRING
STRING
COMPARE这是未给出该选项时的默认行为。
COMPARE option is
not given.
FILE_BASENAME
FILE_BASENAME
NATURAL
NATURAL
STRING,不同之处在于项目中的连续数字按数字顺序排序。这对于对包含嵌入版本号的字符串进行排序最有用。strverscmp()排序规则与C 函数(GNU 扩展)相同。此排序方法仅适用于 CMake 3.18 或更高版本。
STRING, except contiguous digits within an item are sorted
numerically.
This is most useful for sorting strings that contain embedded version numbers.
The sorting rules are the same as for the strverscmp() C function (a GNU
extension).
This sorting method is only available with CMake 3.18 or later.
关键字CASE需要SENSITIVEor INSENSITIVEfor the case,而ORDER关键字需要ASCENDINGor DESCENDINGfor the order。
The CASE keyword requires SENSITIVE or INSENSITIVE for the case,
while the ORDER keyword requires either ASCENDING or DESCENDING for
the order.
对于所有采用索引的列表操作,负索引表示从列表末尾开始计数。以这种方式使用时,列表中的最后一项的索引为 -1,倒数第二项的索引为 -2,依此类推。
For all list operations taking an index, a negative index indicates that counting starts from the end of the list. When used this way, the last item in the list has index -1, the second last -2, and so on.
上面描述了大多数可用的list()子命令。除非另有说明,否则至少从 CMake 3.0 起所有提到的内容都受到支持,因此项目通常应该能够期望它们可用。有关支持的子命令的完整列表,读者应查阅 CMake 文档。
The above describes most of the available list() sub-commands.
Those mentioned are all supported since at least CMake 3.0 unless otherwise
noted, so projects should generally be able to expect them to be available.
For the full list of supported sub-commands, the reader should consult the
CMake documentation.
CMake 通常将分号视为列表分隔符的方式有一个例外。由于历史原因,如果列表项包含左方括号[,它也必须有匹配的右方括号]。CMake 会将这些方括号之间的任何分号视为列表项的一部分,而不是作为列表分隔符。如果尝试用不平衡的方括号构造一个列表,则该列表将不会按预期解释。下面演示了该行为:
There is one exception to the way CMake usually treats semicolons as list
separators.
For historical reasons, if a list item contains an opening square bracket [,
it must also have a matching closing square bracket ].
CMake will consider any semicolon between these square brackets to be part of
the list item instead of as a list separator.
If one tries to construct a list with unbalanced square brackets, the list
won’t be interpreted as expected.
The following demonstrates the behavior:
set(noBrackets "a_a" "b_b")
set(withBrackets "a[a" "b]b")
list(LENGTH noBrackets lenNo)
list(LENGTH withBrackets lenWith)
list(GET noBrackets 0 firstNo)
list(GET withBrackets 0 firstWith)
message("No brackets: "
"Length=${lenNo} --> First_element=${firstNo}"
)
message("With brackets: "
"Length=${lenWith} --> First_element=${firstWith}"
)set(noBrackets "a_a" "b_b")
set(withBrackets "a[a" "b]b")
list(LENGTH noBrackets lenNo)
list(LENGTH withBrackets lenWith)
list(GET noBrackets 0 firstNo)
list(GET withBrackets 0 firstWith)
message("No brackets: "
"Length=${lenNo} --> First_element=${firstNo}"
)
message("With brackets: "
"Length=${lenWith} --> First_element=${firstWith}"
)
上面的输出将是:
The output from the above would be:
无括号:长度=2 --> First_element=a_a 带括号: Length=1 --> First_element=a[a;b]b
No brackets: Length=2 --> First_element=a_a With brackets: Length=1 --> First_element=a[a;b]b
第 8.8.3 节“参数扩展的特殊情况”中的讨论涵盖了这一特性的更多方面。
Discussion in Section 8.8.3, “Special Cases For Argument Expansion” covers further aspects of this peculiarity.
变量操作的另一种常见形式是数学计算。CMake 提供了math()用于执行基本数学评估的命令:
One other common form of variable manipulation is math computation. CMake
provides the math() command for performing basic mathematical evaluation:
math(EXPR outVar mathExpr [OUTPUT_FORMAT format])math(EXPR outVar mathExpr [OUTPUT_FORMAT format])
第一个参数必须是关键字EXPR,同时mathExpr定义要计算的表达式,结果将存储在outVar. 该表达式可以使用以下任何运算符,它们的含义与 C 代码中的含义相同:+ - * / % | & ^ ~ << >>。括号也受支持并具有其通常的数学含义。mathExpr可以使用通常的符号来引用变量${myVar}
。
The first argument must be the keyword EXPR, while mathExpr defines the
expression to be evaluated and the result will be stored in outVar.
The expression may use any of the following operators which all have the same
meaning as they would in C code: + - * / % | & ^ ~ << >>.
Parentheses are also supported and have their usual mathematical meaning.
Variables can be referenced in the mathExpr with the usual ${myVar}
notation.
如果使用 CMake 3.13 或更高版本,OUTPUT_FORMAT可以给出关键字来控制结果如何存储在outVar. format应该是,这DECIMAL是默认行为,或者
HEXADECIMAL。
If using CMake 3.13 or later, the OUTPUT_FORMAT keyword can be given to
control how the result is stored in outVar.
The format should be either DECIMAL, which is the default behavior, or
HEXADECIMAL.
set(x 3)
set(y 7)
math(EXPR zDec "(${x}+${y}) * 2")
message("decimal = ${zDec}")
# Requires CMake 3.13 or later for HEXADECIMAL
math(EXPR zHex "(${x}+${y}) * 2" OUTPUT_FORMAT HEXADECIMAL)
message("hexadecimal = ${zHex}")set(x 3)
set(y 7)
math(EXPR zDec "(${x}+${y}) * 2")
message("decimal = ${zDec}")
# Requires CMake 3.13 or later for HEXADECIMAL
math(EXPR zHex "(${x}+${y}) * 2" OUTPUT_FORMAT HEXADECIMAL)
message("hexadecimal = ${zHex}")
以上产生以下输出:
The above produces the following output:
小数 = 20 十六进制 = 0x14
decimal = 20 hexadecimal = 0x14
在开发环境允许的情况下,CMake GUI 工具是一种快速、轻松地了解项目的构建选项并在开发过程中根据需要修改它们的有用方法。花一点时间熟悉它可以简化以后处理更复杂的项目。它还为开发人员提供了良好的工作基础,以便他们需要尝试编译器设置等内容,因为这些内容可以在 GUI 环境中轻松找到和修改。
Where the development environment allows it, the CMake GUI tool is a useful way to quickly and easily understand the build options for a project and to modify them as needed during development. A little bit of time spent getting familiar with it will simplify working with more complex projects later. It also gives developers a good base to work from should they need to experiment with things like compiler settings, since these are easily found and modified within the GUI environment.
更喜欢提供缓存变量来控制是否启用构建的可选部分,而不是在 CMake 之外的构建脚本中编码逻辑。这使得在 CMake GUI 和其他了解如何使用 CMake 缓存的工具中打开和关闭它们变得微不足道(越来越多的 IDE 环境正在获取此功能)。
Prefer to provide cache variables for controlling whether to enable optional parts of the build instead of encoding the logic in build scripts outside of CMake. This makes it trivial to turn them on and off in the CMake GUI and other tools which understand how to work with the CMake cache (a growing number of IDE environments are acquiring this capability).
除了可能普遍存在PATH或类似的操作系统级变量之外,尽量避免依赖于定义的环境变量。构建应该是可预测的、可靠的且易于设置,但如果它依赖于设置的环境变量才能正常工作,那么这可能会成为新开发人员在努力设置构建环境时感到沮丧的地方。此外,与调用构建本身时相比,运行 CMake 时的环境可能会发生变化。因此,尽可能通过缓存变量将信息直接传递给 CMake。
Try to avoid relying on environment variables being defined, apart from perhaps
the ubiquitous PATH or similar operating system level variables. The build
should be predictable, reliable and easy to set up, but if it relies on
environment variables being set for things to work correctly, this can be a
point of frustration for new developers as they wrestle to get their build
environment set up. Furthermore, the environment at the time CMake is run can
change compared to when the build itself is invoked. Therefore, prefer to pass
information directly to CMake through cache variables instead wherever
possible.
尝试尽早建立变量命名约定。对于缓存变量,请考虑将相关变量分组在公共前缀下,后跟下划线,以利用 CMake GUI 自动根据相同前缀对变量进行分组的方式。还要考虑到该项目有一天可能会成为某个较大项目的子部分,因此可能需要以项目名称开头的名称或与项目密切相关的名称。
Try to establish a variable naming convention early. For cache variables, consider grouping related variables under a common prefix followed by an underscore to take advantage of how CMake GUI groups variables based on the same prefix automatically. Also consider that the project may one day become a sub-part of some larger project, so a name beginning with the project name or something closely associated with the project may be desirable.
尽量避免在项目中定义与缓存变量同名的非缓存变量。对于刚接触 CMake 的开发人员来说,两种类型的变量之间的交互可能是意想不到的。后面的章节还重点介绍了与缓存变量同名的常规变量的其他常见错误和误用。
Try to avoid defining non-cache variables in the project which have the same name as cache variables. The interaction between the two types of variables can be unexpected for developers new to CMake. Later chapters also highlight other common errors and misuses of regular variables that share the same name as cache variables.
CMake 提供了大量预定义变量,这些变量提供有关系统的详细信息或影响 CMake 行为的某些方面。其中一些变量被项目大量使用,例如那些仅在为特定平台构建时定义的变量(WIN32、APPLE、UNIX等)。因此,建议开发人员偶尔快速浏览一下列出预定义变量的 CMake 文档页面,以帮助熟悉可用的内容。
CMake provides a large number of pre-defined variables that provide details
about the system or influence certain aspects of CMake’s behavior. Some of
these variables are heavily used by projects, such as those that are only
defined when building for a particular platform (WIN32, APPLE, UNIX,
etc.). It is therefore recommended for developers to occasionally make a quick
scan through the CMake documentation page listing the pre-defined variables to
help become familiar with what is available.
大多数 CMake 项目的一个常见需求是仅在某些情况下应用某些步骤。例如,项目可能希望仅在特定编译器中或在为特定平台构建时使用某些编译器标志。在其他情况下,项目可能需要迭代一组值或不断重复某些步骤,直到满足特定条件。CMake 以大多数软件开发人员应该熟悉的方式很好地支持这些流控制示例。无处不在的命令提供了预期的 if-then-else 行为,并且通过and
命令if()提供循环。所有三个命令都提供大多数编程语言实现的传统行为,但它们还添加了特定于 CMake 的功能。foreach()while()
A common need for most CMake projects is to apply some steps only in certain
circumstances. Projects may want to use certain compiler flags only with a
particular compiler or when building for a particular platform, for example. In
other cases, the project may need to iterate over a set of values or to keep
repeating some set of steps until a certain condition is met. These examples of
flow control are well supported by CMake in ways which should be familiar to
most software developers. The ubiquitous if() command provides the expected
if-then-else behavior and looping is provided through the foreach() and
while() commands. All three commands provide the traditional behavior as
implemented by most programming languages, but they also have added features
specific to CMake.
该if()命令的现代形式如下(elseif()
可以提供多个子句):
The modern form of the if() command is as follows (multiple elseif()
clauses can be provided):
if(expression1)
# commands ...
elseif(expression2)
# commands ...
else()
# commands ...
endif()if(expression1)
# commands ...
elseif(expression2)
# commands ...
else()
# commands ...
endif()
CMake 的早期版本需要expression1重复作为else()andendif()子句的参数,但自 CMake 2.8.0 以来不再需要这样做。虽然使用旧形式的项目和示例代码仍然很常见,但不鼓励在新项目中使用它,因为它可能会有些难以阅读。新项目应将else()和
endif()参数留空,如上所示。
Very early versions of CMake required expression1 to be repeated as an
argument to the else() and endif() clauses, but this has not been required
since CMake 2.8.0. While it is still not unusual to encounter projects and
example code using that older form, it is discouraged for new projects since it
can be somewhat confusing to read. New projects should leave the else() and
endif() arguments empty, as shown above.
if()和命令中的表达式elseif()可以采用多种不同的形式。CMake 提供传统的布尔逻辑以及各种其他条件,例如文件系统测试、版本比较和事物存在性测试。
The expressions in if() and elseif() commands can take a variety of
different forms. CMake offers the traditional boolean logic as well as various
other conditions such as file system tests, version comparison and testing for
the existence of things.
所有表达式中最基本的是单个常量值:
The most basic of all expressions is a single constant value:
if(value)if(value)
CMake 的判断真假的逻辑比大多数编程语言复杂一些。对于单个不带引号的value,规则如下:
CMake’s logic for what it considers true and false is a little more involved
than most programming languages. For a single unquoted value, the rules are
as follows:
value是带引号或不带引号的常量ON,且值为YES、TRUE或
Y,则将其视为 true。该测试不区分大小写。
value is a quoted or unquoted constant with value ON, YES, TRUE or
Y, it is treated as true. The test is case-insensitive.
value是带引号或不带引号的常量OFF,其值为NO、FALSE、 、
N、IGNORE、NOTFOUND、空字符串或以 结尾的字符串
-NOTFOUND,则将其视为 false。同样,测试不区分大小写。
value is a quoted or unquoted constant with value OFF, NO, FALSE,
N, IGNORE, NOTFOUND, an empty string or a string that ends in
-NOTFOUND, it is treated as false. Again, the test is case-insensitive.
value是一个(可能是浮点数)数字,它将被转换为以下
bool通常的C规则,尽管在此上下文中不经常使用 0 或 1 以外的值。
value is a (possibly floating-point) number, it will be converted to a
bool following usual C rules, although values other than 0 or 1 are not
often used in this context.
在以下示例中,出于if(…)说明目的,仅显示了命令的一部分,并endif()省略了相应的正文:
In the following examples, only the if(…) part of the command is shown for
illustration purposes, the corresponding body and endif() is omitted:
# Examples of quoted and unquoted constants
if(YES)
if("True")
if(0)
if(TRUE)
# These are also treated as unquoted constants because the
# variable evaluation occurs before if() sees the values
set(A YES)
set(B 0)
if(${A}) # Evaluates to true
if(${B}) # Evaluates to false
# Does not match any of the true or false constants, so
# proceed to testing as a variable name in the fall-through
# case below
if(someLetters)
# Quoted value that doesn't match any of the true or false
# constants, so again fall through to testing as a variable
# name or string
if("someLetters")# Examples of quoted and unquoted constants
if(YES)
if("True")
if(0)
if(TRUE)
# These are also treated as unquoted constants because the
# variable evaluation occurs before if() sees the values
set(A YES)
set(B 0)
if(${A}) # Evaluates to true
if(${B}) # Evaluates to false
# Does not match any of the true or false constants, so
# proceed to testing as a variable name in the fall-through
# case below
if(someLetters)
# Quoted value that doesn't match any of the true or false
# constants, so again fall through to testing as a variable
# name or string
if("someLetters")
CMake 文档将失败案例引用为以下形式:
The CMake documentation refers to the fall through case as the following form:
if(<variable|string>)if(<variable|string>)
这在实践中意味着 if 表达式是:
What this means in practice is the if-expression is either:
当使用不带引号的变量名时,变量的值将与假常量进行比较。如果这些都不与值匹配,则表达式的结果为 true。未定义的变量将计算为空字符串,它与 false 常量之一匹配,因此将产生 false 结果。
When an unquoted variable name is used, the variable’s value is compared against the false constants. If none of those match the value, the result of the expression is true. An undefined variable will evaluate to an empty string, which matches one of the false constants and will therefore yield a result of false.
# Common pattern, often used with variables defined
# by commands such as option(enableSomething "...")
if(enableSomething)
# ...
endif()# Common pattern, often used with variables defined
# by commands such as option(enableSomething "...")
if(enableSomething)
# ...
endif()
然而,当 if 表达式是带引号的字符串时,行为会更加复杂:
When the if-expression is a quoted string, however, the behavior is more involved:
CMP0054,请参阅第 12 章,策略)。
CMP0054, see Chapter 12, Policies).
上述两种情况都可能让开发人员感到惊讶,但至少 CMake 3.1 的行为始终是可预测的。当字符串值碰巧与变量名匹配时,3.1 之前的行为有时会导致意外的字符串替换,变量名可能是在距离项目的该部分很远的地方定义的。围绕引用值的潜在混淆意味着通常建议避免在表单中使用引用参数if(something)。通常有更好的比较表达式可以更稳健地处理字符串,这些内容将在下面的第 6.1.3 节“比较测试”中详细介绍。
Both of the above can be a surprise to developers, but at least the CMake 3.1
behavior is always predictable. The pre-3.1 behavior would occasionally
lead to unexpected string substitutions when the string value happened to match
a variable name, possibly one defined somewhere quite far from that part of the
project. The potential confusion around quoted values means it is generally
advisable to avoid using quoted arguments with the if(something) form. There
are usually better comparison expressions that handle strings more robustly,
which are covered in Section 6.1.3, “Comparison Tests” further below.
CMake 支持常用的AND,OR和NOT逻辑运算符,以及用于控制优先顺序的括号。
CMake supports the usual AND, OR and NOT logical operators, as well as
parentheses to control order of precedence.
# Logical operators
if(NOT expression)
if(expression1 AND expression2)
if(expression1 OR expression2)
# Example with parentheses
if(NOT (expression1 AND (expression2 OR expression3)))# Logical operators
if(NOT expression)
if(expression1 AND expression2)
if(expression1 OR expression2)
# Example with parentheses
if(NOT (expression1 AND (expression2 OR expression3)))
按照通常的约定,首先计算括号内的表达式,从最里面的括号开始。
Following usual conventions, expressions inside parentheses are evaluated first, beginning with the innermost parentheses.
CMake 将比较测试分为三个不同的类别:数字、 字符串和版本号,但语法形式都遵循相同的模式:
CMake separates comparison tests into three distinct categories: numeric, string and version numbers, but the syntax forms all follow the same pattern:
if(value1 OPERATOR value2)if(value1 OPERATOR value2)
两个操作数value1和value2可以是变量名或(可能带引号的)值。如果一个值与已定义变量的名称相同,则它将被视为变量。否则,直接将其视为字符串或值。不过,引用的值再次具有与基本一元表达式类似的不明确行为。在 CMake 3.1 之前,带有与变量名称匹配的值的带引号字符串将被该变量的值替换。CMake 3.1 及更高版本的行为使用引用值而不进行替换,这是开发人员直观地期望的。
The two operands, value1 and value2, can be either variable names or
(possibly quoted) values. If a value is the same as the name of a defined
variable, it will be treated as a variable. Otherwise, it is treated as a
string or value directly. Once again though, quoted values have ambiguous
behavior similar to that in basic unary expressions. Prior to CMake 3.1, a
quoted string with a value that matched a variable name would be replaced by
the value of that variable. The behavior of CMake 3.1 and later uses the
quoted value without substitution, which is what developers intuitively expect.
所有三个比较类别都支持同一组操作,但
OPERATOR每个类别的名称不同。下表总结了支持的运算符:
All three comparison categories support the same set of operations, but the
OPERATOR names are different for each category. The following table
summarizes the supported operators:
| 数字 | 细绳 | 版本号 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1仅适用于 CMake 3.7 及更高版本。
1 Only available with CMake 3.7 and later.
数字比较正如人们所期望的那样,将左侧的值与右侧的值进行比较。但请注意,如果任一操作数不是数字,并且当值包含的不仅仅是数字时,其行为不完全符合官方文档,CMake 通常不会引发错误。根据数字和非数字的混合情况,表达式的结果可能为真或为假。
Numeric comparison works as one would expect, comparing the value of the left against the right. Note, however, that CMake does not typically raise an error if either operand is not a number and its behavior does not fully conform to the official documentation when values contain more than just digits. Depending on the mix of digits and non-digits, the result of the expression may be true or false.
# Valid numeric expressions, all evaluating as true
if(2 GREATER 1)
if("23" EQUAL 23)
set(val 42)
if(${val} EQUAL 42)
if("${val}" EQUAL 42)
# Invalid expression that evaluates as true with at
# least some CMake versions. Do not rely on this behavior.
if("23a" EQUAL 23)# Valid numeric expressions, all evaluating as true
if(2 GREATER 1)
if("23" EQUAL 23)
set(val 42)
if(${val} EQUAL 42)
if("${val}" EQUAL 42)
# Invalid expression that evaluates as true with at
# least some CMake versions. Do not rely on this behavior.
if("23a" EQUAL 23)
版本号比较有点像数字比较的增强形式。版本号假定采用以下形式:
major[.minor[.patch[.tweak]]]每个组件均应为非负整数。比较两个版本号时,major首先比较部分。仅当major组件相等时才会minor比较各部分(如果存在),依此类推。缺失的组件被视为零。在以下所有示例中,表达式的计算结果均为 true:
Version number comparisons are somewhat like an enhanced form of numerical
comparisons. Version numbers are assumed to be in the form
major[.minor[.patch[.tweak]]] where each component is expected to be a
non-negative integer. When comparing two version numbers, the major part is
compared first. Only if the major components are equal will the minor parts
be compared (if present) and so on. A missing component is treated as zero. In
all of the following examples, the expression evaluates to true:
if(1.2 VERSION_EQUAL 1.2.0)
if(1.2 VERSION_LESS 1.2.3)
if(1.2.3 VERSION_GREATER 1.2 )
if(2.0.1 VERSION_GREATER 1.9.7)
if(1.8.2 VERSION_LESS 2 )if(1.2 VERSION_EQUAL 1.2.0)
if(1.2 VERSION_LESS 1.2.3)
if(1.2.3 VERSION_GREATER 1.2 )
if(2.0.1 VERSION_GREATER 1.9.7)
if(1.8.2 VERSION_LESS 2 )
版本号比较与数字比较具有相同的稳健性警告。每个版本组件都应为整数,但如果不满足此限制,则比较结果本质上是未定义的。
The version number comparisons have the same robustness caveats as numeric comparisons. Each version component is expected to be an integer, but the comparison result is essentially undefined if this restriction does not hold.
对于字符串,值按字典顺序进行比较。不对字符串的内容做出任何假设,但请注意前面描述的变量/字符串替换情况的可能性。字符串比较是发生此类意外替换的最常见情况之一。
For strings, values are compared lexicographically. No assumptions are made about the contents of the strings, but be mindful of the potential for the variable/string substitution situation described earlier. String comparisons are one of the most common situations where such unexpected substitutions occur.
CMake 还支持根据正则表达式测试字符串:
CMake also supports testing a string against a regular expression:
if(value MATCHES regex)if(value MATCHES regex)
再次value遵循上面定义的变量或字符串规则,并与regex正则表达式进行比较。如果value匹配,则表达式的计算结果为 true。虽然 CMake 文档没有定义if()命令支持的正则表达式语法,但它确实在其他地方为其他命令定义了它(例如,请参阅string()命令文档)。本质上,CMake 仅支持基本的正则表达式语法。
The value again follows the variable-or-string rules defined above and is
compared against the regex regular expression. If the value matches, the
expression evaluates to true. While the CMake documentation doesn’t define the
supported regular expression syntax for if() commands, it does define it
elsewhere for other commands (e.g. see the string() command documentation).
Essentially, CMake supports basic regular expression syntax only.
括号可用于捕获部分匹配值。该命令将设置变量的名称形式为CMAKE_MATCH_<n>where <n>is the group to match。整个匹配的字符串存储在组 0 中。
Parentheses can be used to capture parts of the matched value. The command will
set variables with names of the form CMAKE_MATCH_<n> where <n> is the
group to match. The entire matched string is stored in group 0.
if("Hi from ${who}" MATCHES "Hi from (Fred|Barney).*")
message("${CMAKE_MATCH_1} says hello")
endif()if("Hi from ${who}" MATCHES "Hi from (Fred|Barney).*")
message("${CMAKE_MATCH_1} says hello")
endif()
CMake 还包含一组可用于查询文件系统的测试:
CMake also includes a set of tests which can be used to query the file system:
if(EXISTS pathToFileOrDir)
if(IS_DIRECTORY pathToDir)
if(IS_SYMLINK fileName)
if(IS_ABSOLUTE path)
if(file1 IS_NEWER_THAN file2)if(EXISTS pathToFileOrDir)
if(IS_DIRECTORY pathToDir)
if(IS_SYMLINK fileName)
if(IS_ABSOLUTE path)
if(file1 IS_NEWER_THAN file2)
与大多数其他if表达式不同,无论是否引用,上述运算符都不执行任何没有 的变量/字符串替换${}。
Unlike most other if expressions, none of the above operators perform any
variable/string substitution without ${}, regardless of any quoting.
上述每个运算符都应该是不言自明的,除了
IS_NEWER_THAN。不幸的是,IS_NEWER_THAN对于该操作员所做的事情来说,这是一个不准确的名称。如果两个文件具有相同的时间戳,而不仅仅是 的时间戳file1比 的时间戳新,它也会返回 true file2。这对于只有一秒分辨率的时间戳的文件系统(例如 macOS 10.12 及更早版本上的 HFS+ 文件系统)变得尤为重要。在此类系统上,经常会遇到文件具有相同时间戳的情况,即使这些文件是由单独的命令创建的。另一个不太直观的行为是,如果任一文件丢失,它也会返回 true。此外,如果任一文件未指定为绝对路径,则行为未定义。因此,常常需要IS_NEWER_THAN以否定的方式使用以获得所需的条件。
Each of the above operators should be self-explanatory, except for
IS_NEWER_THAN.
Unfortunately, IS_NEWER_THAN is an inaccurate name for what that operator
does.
It also returns true if both files have the same timestamp, not just if the
timestamp of file1 is newer than that of file2.
This becomes especially important on file systems that only have timestamps
with a resolution of one second, such as the HFS+ file system on macOS 10.12
and earlier.
On such systems, it is very common to encounter scenarios where files have the
same timestamp, even when those files are created by separate commands.
Another less intuitive behavior is that it also returns true if either file
is missing.
Furthermore, if either file is not specified as an absolute path, the behavior
is undefined.
It will often therefore be necessary to use IS_NEWER_THAN in a negated way
to obtain the desired condition.
考虑一个场景,其中secondFile生成自firstFile. 如果firstFile更新或secondFile丢失,则secondFile需要重新创建。如果firstFile不存在,应该是致命错误。这样的逻辑需要这样表达:
Consider a scenario where secondFile is generated from firstFile.
If firstFile is updated or secondFile is missing, then secondFile needs
to be recreated.
If firstFile does not exist, it should be a fatal error.
Such logic would need to be expressed like so:
set(firstFile "/full/path/to/somewhere")
set(secondFile "/full/path/to/another/file")
if(NOT EXISTS ${firstFile})
message(FATAL_ERROR "${firstFile} is missing")
elseif(NOT EXISTS ${secondFile} OR
NOT ${secondFile} IS_NEWER_THAN ${firstFile})
# ... commands to recreate secondFile
endif()set(firstFile "/full/path/to/somewhere")
set(secondFile "/full/path/to/another/file")
if(NOT EXISTS ${firstFile})
message(FATAL_ERROR "${firstFile} is missing")
elseif(NOT EXISTS ${secondFile} OR
NOT ${secondFile} IS_NEWER_THAN ${firstFile})
# ... commands to recreate secondFile
endif()
人们可能会天真地认为该条件可以这样表达:
One might naively think that the condition could be expressed like this instead:
# WARNING: Very likely to be wrong
if(${firstFile} IS_NEWER_THAN ${secondFile})
# ... commands to recreate secondFile
endif()# WARNING: Very likely to be wrong
if(${firstFile} IS_NEWER_THAN ${secondFile})
# ... commands to recreate secondFile
endif()
尽管这些单词可能表达了所需的条件,但它并没有像看起来那样执行,因为如果两个文件具有相同的时间戳,它也会返回 true。如果重新创建的操作secondFile很快并且文件系统只有第二个时间戳分辨率,则很可能secondFile每次运行 CMake 时都会重新创建。如果构建步骤依赖于secondFile,则构建最终也会在每次 CMake 运行后重建这些内容。
Although the words might express the desired condition, it doesn’t do what it
appears to because it also returns true if the two files have the same
timestamp.
If the operation to recreate secondFile is fast and the file system only has
second timestamp resolution, it is very likely that secondFile would be
recreated every time CMake is run.
If build steps depend on secondFile, the build would also end up rebuilding
those things after every CMake run.
最后一类if表达式支持测试各种 CMake 实体是否存在。它们在更大、更复杂的项目中特别有用,其中某些部分可能存在或不存在或启用。
The last category of if expressions support testing whether or not various
CMake entities exist. They can be particularly useful in larger, more complex
projects where some parts might or might not be present or be enabled.
if(DEFINED name)
if(COMMAND name)
if(POLICY name)
if(TARGET name)
if(TEST name) # Available from CMake 3.4 onwardif(DEFINED name)
if(COMMAND name)
if(POLICY name)
if(TARGET name)
if(TEST name) # Available from CMake 3.4 onward
如果指定的实体存在于发出命令name的点,则上述每个都将返回 true 。if
Each of the above will return true if an entity of the specified name exists
at the point where the if command is issued.
DEFINED
DEFINED
name存在,则返回 true。变量的值无关紧要,仅测试其存在性。该变量可以是常规 CMake 变量,也可以是缓存变量。从 CMake 3.14 开始,可以仅使用
CACHE{name}表单检查缓存变量。所有 CMake 版本还支持使用该表单测试环境变量是否存在ENV{name},尽管这只是 CMake 3.13 支持的官方记录。
# Checks for a CMake variable (regular or cache)
if(DEFINED SOMEVAR)
# Checks for a CMake cache variable
if(DEFINED CACHE{SOMEVAR})
# Checks for an environment variable
if(DEFINED ENV{SOMEVAR})
name exists.
The value of the variable is irrelevant, only its existence is tested.
The variable can be a regular CMake variable or it can be a cache variable.
From CMake 3.14, it is possible to check for a cache variable only using the
CACHE{name} form.
All CMake versions also support testing for the existence of an environment
variable using the ENV{name} form, even though this was only officially
documented as supported from CMake 3.13.
# Checks for a CMake variable (regular or cache)
if(DEFINED SOMEVAR)
# Checks for a CMake cache variable
if(DEFINED CACHE{SOMEVAR})
# Checks for an environment variable
if(DEFINED ENV{SOMEVAR})
COMMAND
COMMAND
POLICY
POLICY
CMPxxxx,其中xxxx是四位数字。有关此主题的详细信息,请参阅第 12 章“策略” 。
CMPxxxx, where xxxx is a four digit number.
See Chapter 12, Policies for details on this topic.
TARGET
TARGET
add_executable()、add_library()或 之一
定义,则返回 true add_custom_target()。目标可以定义在任何目录中,只要在if执行测试时已知即可。此测试在引入其他外部项目的复杂项目层次结构中特别有用,并且这些项目可能共享公共的依赖子项目(即,此类if测试可用于在尝试创建目标之前检查目标是否已定义)。
add_executable(), add_library() or
add_custom_target(). The target could have been defined in any directory, as
long as it is known at the point where the if test is performed. This test is
particularly useful in complex project hierarchies that pull in other external
projects and where those projects may share common dependent subprojects (i.e.
this sort of if test can be used to check if a target is already defined
before trying to create it).
TEST
TEST
add_test()(详细内容请参见
第 25 章“测试”)。
add_test() command (covered in detail in
Chapter 25, Testing).
listVar最后,如果包含指定的 ,
则以下形式返回 true value,其中value遵循通常的变量或字符串规则,但listVar
必须是列表变量的名称。
Lastly, the following form returns true if listVar contains the specified
value, where value follows the usual variable-or-string rules but listVar
must be the name of a list variable.
if(value IN_LIST listVar) # Available since CMake 3.3if(value IN_LIST listVar) # Available since CMake 3.3
有一些用途if()非常常见,值得特别提及。其中许多逻辑依赖于预定义的 CMake 变量,尤其是与编译器和目标平台相关的变量。不幸的是,基于错误变量的表达式很常见。例如,考虑一个具有两个 C++ 源文件的项目,一个用于使用 Visual Studio 编译器或与其兼容的编译器(例如 Intel)构建,另一个用于使用所有其他编译器构建。这种逻辑经常像这样实现:
A few uses of if() are so common, they deserve special mention. Many of these
rely on predefined CMake variables for their logic, especially variables
relating to the compiler and target platform. Unfortunately, it is common to
see such expressions based on the wrong variables. For example, consider a
project which has two C++ source files, one for building with Visual
Studio compilers or those compatible with them (e.g. Intel) and another for
building with all other compilers. Such logic is frequently implemented like
so:
if(WIN32)
set(platformImpl source_win.cpp)
else()
set(platformImpl source_generic.cpp)
endif()if(WIN32)
set(platformImpl source_win.cpp)
else()
set(platformImpl source_generic.cpp)
endif()
虽然这可能适用于大多数项目,但它实际上并没有表达正确的约束。例如,考虑一个在 Windows 上构建但使用 MinGW 编译器的项目。对于这种情况,source_generic.cpp可能是更合适的源文件。上述可以更准确地实现如下:
While this will likely work for the majority of projects, it doesn’t actually
express the right constraint. Consider, for example, a project built on Windows
but using the MinGW compiler. For such cases, source_generic.cpp may be the
more appropriate source file. The above could be more accurately implemented as
follows:
if(MSVC)
set(platformImpl source_msvc.cpp)
else()
set(platformImpl source_generic.cpp)
endif()if(MSVC)
set(platformImpl source_msvc.cpp)
else()
set(platformImpl source_generic.cpp)
endif()
另一个示例涉及基于所使用的 CMake 生成器的条件行为。特别是,CMake 在使用 Xcode 生成器构建时提供了其他生成器不支持的附加功能。项目有时会假设为 macOS 进行构建意味着将使用 Xcode 生成器,但情况并非必须如此(而且通常并非如此)。有时会使用以下不正确的逻辑:
Another example involves conditional behavior based on the CMake generator being used. In particular, CMake offers additional features when building with the Xcode generator which no other generators support. Projects sometimes make the assumption that building for macOS means the Xcode generator will be used, but this doesn’t have to be the case (and often isn’t). The following incorrect logic is sometimes used:
if(APPLE)
# Some Xcode-specific settings here...
else()
# Things for other platforms here...
endif()if(APPLE)
# Some Xcode-specific settings here...
else()
# Things for other platforms here...
endif()
同样,这似乎做了正确的事情,但如果开发人员尝试在 macOS 上使用不同的生成器(例如 Ninja 或 Unix Makefiles),逻辑就会失败。使用表达式测试平台APPLE并不能表达正确的条件,应该测试 CMake 生成器:
Again, this may seem to do the right thing, but if a developer tries to
use a different generator (e.g. Ninja or Unix Makefiles) on macOS, the logic
fails. Testing the platform with the expression APPLE doesn’t express the
right condition, the CMake generator should be tested instead:
if(CMAKE_GENERATOR STREQUAL "Xcode")
# Some Xcode-specific settings here...
else()
# Things for other CMake generators here...
endif()if(CMAKE_GENERATOR STREQUAL "Xcode")
# Some Xcode-specific settings here...
else()
# Things for other CMake generators here...
endif()
上面的例子都是测试平台而不是约束实际涉及的实体的情况。这是可以理解的,因为平台是最容易理解和测试的事物之一,但是使用它而不是更准确的约束可能会不必要地限制开发人员可用的生成器选择,或者可能会导致完全错误的行为。
The above examples are both cases of testing the platform instead of the entity the constraint actually relates to. This is understandable, since the platform is one of the simplest things to understand and test, but using it instead of the more accurate constraint can unnecessarily limit the generator choices available to developers, or it may result in the wrong behavior entirely.
另一个常见的例子,这次使用得当,是根据是否设置了特定的 CMake 选项来有条件地包含目标。
Another common example, this time used appropriately, is the conditional inclusion of a target based on whether or not a particular CMake option has been set.
option(BUILD_MYLIB "Enable building the MyLib target")
if(BUILD_MYLIB)
add_library(MyLib src1.cpp src2.cpp)
endif()option(BUILD_MYLIB "Enable building the MyLib target")
if(BUILD_MYLIB)
add_library(MyLib src1.cpp src2.cpp)
endif()
更复杂的项目通常使用上述模式有条件地包含子目录或基于 CMake 选项或缓存变量执行各种其他任务。然后,开发人员可以打开/关闭该选项或将变量设置为非默认值,而无需CMakeLists.txt直接编辑文件。这对于由持续集成系统等驱动的脚本化构建特别有用,这些系统可能想要启用或禁用构建的某些部分。
More complex projects often use the above pattern to conditionally include
subdirectories or perform a variety of other tasks based on a CMake option or
cache variable. Developers can then turn that option on/off or set the variable
to non-default values without having to edit the CMakeLists.txt file
directly. This is especially useful for scripted builds driven by continuous
integration systems, etc. which may want to enable or disable certain parts of
the build.
许多 CMake 项目中的另一个常见需求是对项目列表或一系列值执行某些操作。或者,可能需要重复执行某些操作,直到满足特定条件。CMake 很好地满足了这些需求,提供了传统行为和一些附加功能,使使用 CMake 功能变得更加容易。
Another common need in many CMake projects is to perform some action on a list of items or for a range of values. Alternatively, some action may need to be performed repeatedly until a particular condition is met. These needs are well covered by CMake, offering the traditional behavior with some additions to make working with CMake features a little easier.
CMake 提供了foreach()使项目能够迭代一组项目或值的命令。有几种不同的形式foreach(),其中最基本的是:
CMake provides the foreach() command to enable projects to iterate over a
set of items or values. There are a few different forms of foreach(), the
most basic of which is:
foreach(loopVar arg1 arg2 ...)
# ...
endforeach()foreach(loopVar arg1 arg2 ...)
# ...
endforeach()
在上面的形式中,对于每个argN值,loopVar都设置为该参数并执行循环体。不执行变量/字符串测试,参数完全按照指定的值使用。参数也可以使用更通用的命令形式由一个或多个列表变量指定,而不是显式列出每一项:
In the above form, for each argN value, loopVar is set to that argument and
the loop body is executed. No variable/string test is performed, the arguments
are used exactly as the values are specified. Rather than listing out each item
explicitly, the arguments can also be specified by one or more list variables
using the more general form of the command:
foreach(loopVar IN [LISTS listVar1 ...] [ITEMS item1 ...])
# ...
endforeach()foreach(loopVar IN [LISTS listVar1 ...] [ITEMS item1 ...])
# ...
endforeach()
在这种更通用的形式中,仍然可以使用关键字指定单个参数
ITEMS,但LISTS关键字允许指定一个或多个列表变量。使用这种更通用的形式时,必须提供其中一个ITEMS或(或两者)。LISTS当两者都提供时,ITEMS必须出现在 后面LISTS。列表变量允许listVarN保存空列表。一个例子应该有助于阐明这种更通用的形式的用法。
In this more general form, individual arguments can still be specified using the
ITEMS keyword, but the LISTS keyword allows one or more list variables to be
specified. Either ITEMS or LISTS (or both) must be provided when using
this more general form. When both are provided, the ITEMS must appear after
the LISTS. It is permitted for the listVarN list variables to hold an empty
list. An example should help clarify this more general form’s usage.
set(list1 A B)
set(list2)
set(foo WillNotBeShown)
foreach(loopVar IN LISTS list1 list2 ITEMS foo bar)
message("Iteration for: ${loopVar}")
endforeach()set(list1 A B)
set(list2)
set(foo WillNotBeShown)
foreach(loopVar IN LISTS list1 list2 ITEMS foo bar)
message("Iteration for: ${loopVar}")
endforeach()
上面的输出将是:
The output from the above would be:
迭代:A 迭代:B 迭代: foo 迭代:酒吧
Iteration for: A Iteration for: B Iteration for: foo Iteration for: bar
CMake 3.17 添加了一种更专门的形式,用于一次循环多个列表:
CMake 3.17 added a more specialized form for looping over multiple lists at once:
foreach(loopVar... IN ZIP_LISTS listVar...)
# ...
endforeach()foreach(loopVar... IN ZIP_LISTS listVar...)
# ...
endforeach()
如果只给出一个,则该命令将在每次迭代时loopVar设置以下形式的变量,其中对应于该
变量。编号从 0 开始。如果每个都有一个,则该命令会将它们一对一映射,而不是创建变量。以下示例演示了两种情况:loopVar_NNlistVarNloopVarlistVarloopVar_N
If only one loopVar is given, then the command will set variables of the
form loopVar_N at each iteration, where N corresponds to the listVarN
variable.
Numbering starts from 0.
If there is one loopVar for each listVar, then the command maps them
one-to-one instead of creating loopVar_N variables.
The following example demonstrates the two cases:
set(list0 A B)
set(list1 one two)
foreach(var0 var1 IN ZIP_LISTS list0 list1)
message("Vars: ${var0} ${var1}")
endforeach()
foreach(var IN ZIP_LISTS list0 list1)
message("Vars: ${var_0} ${var_1}")
endforeach()set(list0 A B)
set(list1 one two)
foreach(var0 var1 IN ZIP_LISTS list0 list1)
message("Vars: ${var0} ${var1}")
endforeach()
foreach(var IN ZIP_LISTS list0 list1)
message("Vars: ${var_0} ${var_1}")
endforeach()
两个foreach()循环将打印相同的输出:
Both foreach() loops will print the same output:
变量:一 变量:B 2
Vars: A one Vars: B two
以这种方式“压缩”的列表不必具有相同的长度。当迭代移过较短列表的末尾时,关联的迭代变量将是未定义的。获取未定义变量的值会产生空字符串。下一个示例演示了该行为:
The lists to be "zipped" in this way do not have to be the same length. The associated iteration variable will be undefined when iteration moves past the end of the shorter list. Taking the value of an undefined variable results in an empty string. The next example demonstrates the behavior:
set(long A B C)
set(short justOne)
foreach(varLong varShort IN ZIP_LISTS long short)
message("Vars: ${varLong} ${varShort}")
endforeach()set(long A B C)
set(short justOne)
foreach(varLong varShort IN ZIP_LISTS long short)
message("Vars: ${varLong} ${varShort}")
endforeach()
变量:justOne 变量:B 变量:C
Vars: A justOne Vars: B Vars: C
该foreach()命令还支持在一系列数值上进行更像 C 的迭代:
The foreach() command also supports the more C-like iteration over a range of
numerical values:
foreach(loopVar RANGE start stop [step])foreach(loopVar RANGE start stop [step])
当使用RANGE的形式时foreach(),循环将被loopVar
设置为范围startto stop(含)内的每个值来执行。如果step
提供了该选项,则每次迭代后该值都会添加到前一个值,并且当结果大于 时循环停止stop。
When using the RANGE form of foreach(), the loop is executed with loopVar
set to each value in the range start to stop (inclusive). If the step
option is provided, then this value is added to the previous one after each
iteration and the loop stops when the result of that is greater than stop.
该RANGE表单也只接受一个参数,如下所示:
The RANGE form also accepts just one argument like so:
foreach(loopVar RANGE value)foreach(loopVar RANGE value)
这相当于foreach(loopVar RANGE 0 value),这意味着循环体将执行(value + 1)多次。这是不幸的,因为更直观的期望可能是循环体执行value次数。RANGE
因此,避免使用第二种形式并显式指定start和值可能会更清楚stop。
This is equivalent to foreach(loopVar RANGE 0 value), which means the loop
body will execute (value + 1) times. This is unfortunate, since the more
intuitive expectation is probably that the loop body executes value times.
For this reason, it is likely to be clearer to avoid using this second RANGE
form and explicitly specify both the start and stop values instead.
if()与和命令的情况类似endif(),在 CMake 的早期版本(即 2.8.0 之前)中,所有形式的foreach()命令都要求将loopVar指定为 的参数endforeach()。同样,这会损害可读性并且几乎没有什么好处,因此
不鼓励在新项目中指定loopVarwith 。endforeach()
Similar to the situation for the if() and endif() commands, in very early
versions of CMake (i.e. prior to 2.8.0), all forms of the foreach() command
required that the loopVar also be specified as an argument to endforeach().
Again, this harms readability and offers little benefit, so specifying the
loopVar with endforeach() is discouraged for new projects.
CMake 提供的另一个循环命令是while():
The other looping command offered by CMake is while():
while(condition)
# ...
endwhile()while(condition)
# ...
endwhile()
进行condition测试,如果其计算结果为 true(遵循与语句中的表达式相同的规则if()),则执行循环体。重复此操作,直到condition计算结果为 false 或提前退出循环(请参阅下一节)。同样,在 2.8.0 之前的 CMake 版本中,condition
必须在endwhile()命令中重复 ,但这不再是必要的,并且对于新项目来说是积极劝阻的。
The condition is tested and if it evaluates to true (following the same rules
as the expression in if() statements), then the loop body is executed. This
is repeated until condition evaluates to false or the loop is exited early
(see next section). Again, in CMake versions prior to 2.8.0, the condition
had to be repeated in the endwhile() command, but this is no longer necessary
and is actively discouraged for new projects.
while()和循环都foreach()支持使用 提前退出循环break()或使用 跳到下一次迭代开始的
功能continue()。这些命令的行为就像它们类似命名的 C 语言对应命令一样,并且都仅在最内层的封闭循环上运行。以下示例说明了该行为。
Both while() and foreach() loops support the ability to exit the loop early
with break() or to skip to the start of the next iteration with
continue(). These commands behave just like their similarly named C
language counterparts and both operate only on the inner-most enclosing loop.
The following example illustrates the behavior.
foreach(outerVar IN ITEMS a b c)
unset(s)
foreach(innerVar IN ITEMS 1 2 3)
# Stop inner loop once string s gets long
list(APPEND s "${outerVar}${innerVar}")
string(LENGTH "${s}" length)
if(length GREATER 5)
break() ①
endif()
# Do no more processing if outerVar is "b"
if(outerVar STREQUAL "b")
continue() ②
endif()
message("Processing ${outerVar}-${innerVar}")
endforeach()
message("Accumulated list: ${s}")
endforeach()foreach(outerVar IN ITEMS a b c)
unset(s)
foreach(innerVar IN ITEMS 1 2 3)
# Stop inner loop once string s gets long
list(APPEND s "${outerVar}${innerVar}")
string(LENGTH "${s}" length)
if(length GREATER 5)
break() ①
endif()
# Do no more processing if outerVar is "b"
if(outerVar STREQUAL "b")
continue() ②
endif()
message("Processing ${outerVar}-${innerVar}")
endforeach()
message("Accumulated list: ${s}")
endforeach()
innerVarforeach 循环。innerVar foreach loop early.innerVar 迭代并移至下一个innerVar
项目。innerVar iteration and moves on to the next innerVar
item.上面示例的输出将是:
The output from the above example would be:
处理a-1 处理a-2 累计列表:a1;a2;a3 累计列表:b1;b2;b3 处理c-1 处理c-2 累计列表:c1;c2;c3
Processing a-1 Processing a-2 Accumulated list: a1;a2;a3 Accumulated list: b1;b2;b3 Processing c-1 Processing c-2 Accumulated list: c1;c2;c3
最大限度地减少字符串被无意解释为if(),foreach()和while()命令中的变量的机会。避免带引号的一元表达式,更喜欢使用字符串比较操作。强烈建议将最低 CMake 版本设置为至少 3.1,以禁用允许将带引号的字符串值隐式转换为变量名称的旧行为。
Minimize opportunities for strings to be unintentionally interpreted as
variables in if(), foreach() and while() commands. Avoid unary
expressions with quotes, prefer to use a string comparison operation instead.
Strongly prefer to set a minimum CMake version of at least 3.1 to disable the
old behavior that allowed implicit conversion of quoted string values to
variable names.
当需要在命令和组捕获变量中进行正则表达式匹配时if(xxx MATCHES regex),通常建议
CMAKE_MATCH_<n>尽快将匹配结果存储在普通变量中。这些变量将被执行任何类型的正则表达式操作的下一个命令覆盖。
When regular expression matching in if(xxx MATCHES regex) commands and the
group capture variables are needed, it is generally advisable to store the
CMAKE_MATCH_<n> match results in ordinary variables as soon as possible.
These variables will be overwritten by the next command that does any sort of
regular expression operation.
更喜欢使用循环命令,以避免出现歧义或误导性的代码。如果使用 的 RANGE 形式foreach(),请始终指定起始值和结束值。如果迭代项目,请考虑使用IN LISTS或
IN ITEMS表单是否比裸foreach(loopVar item1 item2 …)表单更清楚地传达正在执行的操作。
Prefer to use looping commands which avoid ambiguous or misleading code. If
using the RANGE form of foreach(), always specify both the start and end
values. If iterating over items, consider whether using the IN LISTS or
IN ITEMS forms communicate more clearly what is being done rather than a
bare foreach(loopVar item1 item2 …) form.
对于简单的项目,将所有内容保留在一个目录中是可以的,但大多数现实世界的项目倾向于将文件拆分到多个目录中。通常会发现不同的文件类型或单独的模块分组在自己的目录下,或者属于逻辑功能分组的文件位于项目目录层次结构中自己的部分中。虽然目录结构可能由开发人员如何看待项目驱动,但项目的结构方式也会影响构建系统。
For simple projects, keeping everything in one directory is fine, but most real world projects tend to split their files across multiple directories. It is common to find different file types or individual modules grouped under their own directories, or for files belonging to logical functional groupings to be in their own part of the project’s directory hierarchy. While the directory structure may be driven by how developers think of the project, the way the project is structured also impacts the build system.
任何多目录项目中的两个基本 CMake 命令是
add_subdirectory()和include()。这些命令将另一个文件或目录中的内容带入构建中,从而允许构建逻辑分布在目录层次结构中,而不是强制在最顶层定义所有内容。这具有许多优点:
Two fundamental CMake commands in any multi-directory project are
add_subdirectory() and include(). These commands bring content from another
file or directory into the build, allowing the build logic to be distributed
across the directory hierarchy rather than forcing everything to be defined at
the top-most level. This offers a number of advantages:
add_subdirectory()并且include()具有截然不同的特征,因此了解两者的优点和缺点很重要。
add_subdirectory() and include() have quite different characteristics, so it
is important to understand the strengths and weaknesses of both.
该add_subdirectory()命令允许项目将另一个目录带入构建中。该目录必须有自己的CMakeLists.txt文件,该文件将在调用时进行处理add_subdirectory(),并在项目的构建树中为其创建相应的目录。
The add_subdirectory() command allows a project to bring another directory
into the build. That directory must have its own CMakeLists.txt file which
will be processed at the point where add_subdirectory() is called and a
corresponding directory will be created for it in the project’s build tree.
add_subdirectory(sourceDir [binaryDir] [EXCLUDE_FROM_ALL])add_subdirectory(sourceDir [binaryDir] [EXCLUDE_FROM_ALL])
尽管通常是,但不一定sourceDir是源树中的子目录。可以添加任何目录,sourceDir指定为绝对路径或相对路径,后者相对于当前源目录。通常仅在添加主源树外部的目录时才需要绝对路径。
The sourceDir does not have to be a subdirectory within the source tree,
although it usually is. Any directory can be added, with sourceDir being
specified as either an absolute or relative path, the latter being
relative to the current source directory. Absolute paths are typically only
needed when adding directories that are outside the main source tree.
通常binaryDir不需要指定。省略时,CMake 在构建树中创建一个与sourceDir. 如果
sourceDir包含任何路径组件,这些组件将镜像到
binaryDirCMake 创建的路径中。或者,binaryDir可以显式指定为绝对路径或相对路径,后者相对于当前二进制目录进行评估(稍后将更详细地讨论)。如果sourceDir是源树外部的路径,则 CMake 需要
binaryDir指定 ,因为无法再自动构造相应的相对路径。
Normally, the binaryDir does not need to be specified. When omitted, CMake
creates a directory in the build tree with the same name as the sourceDir. If
sourceDir contains any path components, these will be mirrored in the
binaryDir created by CMake. Alternatively, the binaryDir can be explicitly
specified as either an absolute or relative path, with the latter being
evaluated relative to the current binary directory (discussed in more detail
shortly). If sourceDir is a path outside the source tree, CMake requires the
binaryDir to be specified since a corresponding relative path can no longer
be constructed automatically.
可选关键字旨在控制正在添加的子目录中定义的目标是否应默认EXCLUDE_FROM_ALL包含在项目的
目标中。ALL不幸的是,对于某些 CMake 版本和项目生成器,它并不总是按预期运行,甚至可能导致构建损坏。
The optional EXCLUDE_FROM_ALL keyword is intended to control whether targets
defined in the subdirectory being added should be included in the project’s
ALL target by default. Unfortunately, for some CMake versions and project
generators, it doesn’t always act as expected and can even result in broken
builds.
有时,开发人员需要知道与当前源目录对应的构建目录的位置,例如在复制运行时所需的文件或执行自定义构建任务时。使用
add_subdirectory(),源树和构建树的目录结构都可以是任意复杂的。甚至可能有多个构建树与同一源树一起使用。因此,开发人员需要 CMake 的一些帮助来确定感兴趣的目录。为此,CMake 提供了许多变量来跟踪CMakeLists.txt当前正在处理的文件的源目录和二进制目录。当 CMake 处理每个文件时,以下只读变量会自动更新。它们始终包含绝对路径。
Sometimes a developer needs to know the location of the build directory
corresponding to the current source directory, such as when copying files
needed at run time or to perform a custom build task. With
add_subdirectory(), both the source and the build trees’ directory
structures can be arbitrarily complex. There could even be multiple build trees
being used with the same source tree. The developer therefore needs some
assistance from CMake to determine the directories of interest. To that end,
CMake provides a number of variables which keep track of the source and binary
directories for the CMakeLists.txt file currently being processed. The
following read-only variables are updated automatically as each file is
processed by CMake. They always contain absolute paths.
CMAKE_SOURCE_DIR
CMAKE_SOURCE_DIR
CMakeLists.txt文件所在的位置)。该变量永远不会改变其值。
CMakeLists.txt file resides). This variable never changes
its value.
CMAKE_BINARY_DIR
CMAKE_BINARY_DIR
CMAKE_CURRENT_SOURCE_DIR
CMAKE_CURRENT_SOURCE_DIR
CMakeLists.txt当前由 CMake 处理的文件的目录。每次由于调用而处理新文件时,它都会更新add_subdirectory(),并在该目录的处理完成时再次恢复。
CMakeLists.txt file
currently being processed by CMake. It is updated each time a new file is
processed as a result of an add_subdirectory() call and is restored back
again when processing of that directory is complete.
CMAKE_CURRENT_BINARY_DIR
CMAKE_CURRENT_BINARY_DIR
CMakeLists.txt与 CMake 当前正在处理的文件对应的构建目录
。它会随着每次调用而改变,add_subdirectory()并在返回时再次恢复add_subdirectory()
。
CMakeLists.txt file currently being processed by CMake. It changes for every
call to add_subdirectory() and is restored again when add_subdirectory()
returns.
一个示例应该有助于演示该行为:
An example should help demonstrate the behavior:
cmake_minimum_required(VERSION 3.0)
project(MyApp)
message("top: CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("top: CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("top: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("top: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
add_subdirectory(mysub)
message("top: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("top: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")cmake_minimum_required(VERSION 3.0)
project(MyApp)
message("top: CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("top: CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("top: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("top: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
add_subdirectory(mysub)
message("top: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("top: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
message("mysub: CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("mysub: CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("mysub: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("mysub: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")message("mysub: CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("mysub: CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("mysub: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("mysub: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
对于上面的示例,如果顶级CMakeLists.txt文件位于该目录中/somewhere/src并且构建目录为/somewhere/build,则将生成以下输出:
For the above example, if the top level CMakeLists.txt file was in the
directory /somewhere/src and the build directory was /somewhere/build,
the following output would be generated:
顶部:CMAKE_SOURCE_DIR = /某处/src 顶部:CMAKE_BINARY_DIR = /某处/构建 顶部:CMAKE_CURRENT_SOURCE_DIR = /某处/src 顶部:CMAKE_CURRENT_BINARY_DIR = /某处/构建 mysub:CMAKE_SOURCE_DIR = /某处/src mysub:CMAKE_BINARY_DIR = /某处/构建 mysub: CMAKE_CURRENT_SOURCE_DIR = /somewhere/src/mysub mysub: CMAKE_CURRENT_BINARY_DIR = /somewhere/build/mysub 顶部:CMAKE_CURRENT_SOURCE_DIR = /某处/src 顶部:CMAKE_CURRENT_BINARY_DIR = /某处/构建
top: CMAKE_SOURCE_DIR = /somewhere/src top: CMAKE_BINARY_DIR = /somewhere/build top: CMAKE_CURRENT_SOURCE_DIR = /somewhere/src top: CMAKE_CURRENT_BINARY_DIR = /somewhere/build mysub: CMAKE_SOURCE_DIR = /somewhere/src mysub: CMAKE_BINARY_DIR = /somewhere/build mysub: CMAKE_CURRENT_SOURCE_DIR = /somewhere/src/mysub mysub: CMAKE_CURRENT_BINARY_DIR = /somewhere/build/mysub top: CMAKE_CURRENT_SOURCE_DIR = /somewhere/src top: CMAKE_CURRENT_BINARY_DIR = /somewhere/build
在第五章“变量”中,简要提到了作用域的概念。调用的效果之一add_subdirectory()是 CMake 创建一个新的作用域来处理该目录的CMakeLists.txt文件。该新作用域的作用类似于调用作用域的子作用域,并且有许多效果:
In Chapter 5, Variables, the concept of scope was mentioned briefly. One of the
effects of calling add_subdirectory() is that CMake creates a new scope for
processing that directory’s CMakeLists.txt file. That new scope acts like a
child of the calling scope and there are a number of effects:
换句话说,在进入子作用域时,它会接收当时在调用作用域中定义的所有变量的副本。对子进程中变量的任何更改都会在子进程的副本上执行,从而使调用者的变量保持不变。一个例子最能说明这种行为:
Put another way, upon entry into the child scope, it receives a copy of all of the variables defined in the calling scope at that point in time. Any changes to variables in the child are performed on the child’s copy, leaving the caller’s variables unchanged. An example best illustrates the behavior:
set(myVar foo)
message("Parent (before): myVar = ${myVar}")
message("Parent (before): childVar = ${childVar}")
add_subdirectory(subdir)
message("Parent (after): myVar = ${myVar}")
message("Parent (after): childVar = ${childVar}")set(myVar foo)
message("Parent (before): myVar = ${myVar}")
message("Parent (before): childVar = ${childVar}")
add_subdirectory(subdir)
message("Parent (after): myVar = ${myVar}")
message("Parent (after): childVar = ${childVar}")
message("Child (before): myVar = ${myVar}")
message("Child (before): childVar = ${childVar}")
set(myVar bar)
set(childVar fuzz)
message("Child (after): myVar = ${myVar}")
message("Child (after): childVar = ${childVar}")message("Child (before): myVar = ${myVar}")
message("Child (before): childVar = ${childVar}")
set(myVar bar)
set(childVar fuzz)
message("Child (after): myVar = ${myVar}")
message("Child (after): childVar = ${childVar}")
这会产生以下输出:
This produces the following output:
父级(之前):myVar = foo ① 父级(之前):childVar = ② 子级(之前):myVar = foo ③ 子级(之前):childVar = ④ 子级(之后):myVar = bar ⑤ 子级(之后):childVar = fuzz ⑥ 父级(之后):myVar = foo ⑦ 父级(之后):childVar = ⑧
Parent (before): myVar = foo ① Parent (before): childVar = ② Child (before): myVar = foo ③ Child (before): childVar = ④ Child (after): myVar = bar ⑤ Child (after): childVar = fuzz ⑥ Parent (after): myVar = foo ⑦ Parent (after): childVar = ⑧
myVar是在父级定义的。myVar is defined at the parent level.childVar未在父级别定义,因此其计算结果为空字符串。childVar is not defined at the parent level, so it evaluates to an empty
string.myVar在子作用域中仍然可见。myVar is still visible in the child scope.childVar在设置之前,子作用域中仍然未定义。childVar is still undefined in the child scope before it is set.myVar在子作用域中修改。myVar is modified in the child scope.childVaris 已在子范围中设置。childVar is has been set in the child scope.myVar仍然具有调用之前的值add_subdirectory()。子作用域中的修改对myVar
父作用域不可见。myVar still has the value
from before the call to add_subdirectory(). The modification to myVar
in the child scope is not visible to the parent.childVar是在子作用域中定义的,因此它对父作用域不可见,并且计算结果为空字符串。childVar was defined in the child scope, so it is not visible to the
parent and evaluates to an empty string.上述变量作用域行为突出了 的重要特征之一add_subdirectory()。它允许添加的目录更改它想要的任何变量,而不影响调用范围中的变量。这有助于使调用范围与潜在不需要的更改隔离。
The above behavior of scoping for variables highlights one of the important
characteristics of add_subdirectory(). It allows the added directory to
change whatever variables it wants without affecting variables in the calling
scope. This helps keep the calling scope isolated from potentially unwanted
changes.
然而,有时需要在添加的目录中进行的变量更改对调用者可见。例如,目录可能负责收集一组源文件名并将其作为文件列表传递回父级。PARENT_SCOPE
这就是命令中关键字的用途set()。当PARENT_SCOPE使用 时,所设置的变量是父作用域中的变量,而不是当前作用域中的变量。重要的是,这并不意味着在父作用域和当前作用域中都设置变量。稍微修改一下前面的例子,效果就
PARENT_SCOPE清晰了:
There are times, however, where it is desirable for a variable change made in
an added directory to be visible to the caller. For example, the directory may
be responsible for collecting a set of source file names and passing it back up
to the parent as a list of files. This is the purpose of the PARENT_SCOPE
keyword in the set() command. When PARENT_SCOPE is used, the variable being
set is the one in the parent scope, not the one in the current scope.
Importantly, it does not mean set the variable in both the parent and the
current scope. Modifying the previous example slightly, the effect of
PARENT_SCOPE becomes clear:
set(myVar foo)
message("Parent (before): myVar = ${myVar}")
add_subdirectory(subdir)
message("Parent (after): myVar = ${myVar}")set(myVar foo)
message("Parent (before): myVar = ${myVar}")
add_subdirectory(subdir)
message("Parent (after): myVar = ${myVar}")
message("Child (before): myVar = ${myVar}")
set(myVar bar PARENT_SCOPE)
message("Child (after): myVar = ${myVar}")message("Child (before): myVar = ${myVar}")
set(myVar bar PARENT_SCOPE)
message("Child (after): myVar = ${myVar}")
这会产生以下输出:
This produces the following output:
父级(之前):myVar = foo 子级(之前):myVar = foo 子级(之后):myVar = foo ① 父级(之后):myVar = bar ②
Parent (before): myVar = foo Child (before): myVar = foo Child (after): myVar = foo ① Parent (after): myVar = bar ②
myVar作用域中的 不受调用影响set(),因为该PARENT_SCOPE关键字告诉 CMake 修改父作用域的myVar,而不是本地作用域的 。myVar in the child scope is not affected by the set() call because
the PARENT_SCOPE keyword tells CMake to modify the parent’s myVar, not
the local one.myVar已set()被子级范围中的调用修改。myVar has been modified by the set() call in the child
scope.由于使用 可以PARENT_SCOPE防止命令修改任何同名的局部变量,因此如果局部作用域不重用与父级作用域相同的变量名,则可以减少误导性。在上面的示例中,更清晰的命令集是:
Because the use of PARENT_SCOPE prevents any local variable of the same name
from being modified by the command, it can be less misleading if the local
scope does not reuse the same variable name as one from the parent. In the
above example, a clearer set of commands would be:
set(localVar bar)
set(myVar ${localVar} PARENT_SCOPE)set(localVar bar)
set(myVar ${localVar} PARENT_SCOPE)
显然,上面是一个简单的示例,但对于现实世界的项目,可能有许多命令有助于localVar
在最终设置父myVar变量之前构建 的值。
Obviously the above is a trivial example, but for real world projects, there
may be many commands which contribute to building up the value of localVar
before finally setting the parent’s myVar variable.
不仅仅是变量受范围、策略和某些属性的影响,在这方面,某些属性也具有与变量类似的行为。对于策略,每次add_subdirectory()调用都会创建一个新范围,可以在其中进行策略更改,而不会影响父级的策略设置。同样,可以在子目录的CMakeLists.txt文件中设置目录属性,这不会影响父目录的属性。这两章都在各自的章节中进行了更详细的介绍:第 12 章“策略”和第 9 章“属性”。
It’s not just variables that are affected by scope, policies and some
properties also have similar behavior to variables in this regard. In the case
of policies, each add_subdirectory() call creates a new scope in which policy
changes can be made without affecting the policy settings of the parent.
Similarly, there are directory properties which can be set in the child
directory’s CMakeLists.txt file which will have no effect on the parent’s
directory properties. Both of these are covered in more detail in their own
respective chapters: Chapter 12, Policies and Chapter 9, Properties.
有时会出现一个问题是是否调用project()子目录
CMakeLists.txt的文件。在大多数情况下,没有必要或不希望这样做,但这是允许的。唯一必须调用的地方project()是最顶层的
CMakeLists.txt文件。读取顶级CMakeLists.txt文件后,CMake 会扫描该文件的内容,查找对project(). 如果没有找到这样的调用,CMake 将发出警告并插入一个内部调用,project()并启用默认的 C 和 C++ 语言。项目永远不应该依赖这种机制,它们应该始终显式地调用project()自己。project()请注意,通过包装函数或通过 viaadd_subdirectory()或读入的文件来调用是不够的include(),顶层
文件必须直接CMakeLists.txt调用。project()
A question that sometimes arises is whether to call project() in the
CMakeLists.txt files of subdirectories.
In most cases, it is not necessary or desirable to do so, but it is permitted.
The only place where project() must be called is the top-most
CMakeLists.txt file.
Upon reading the top level CMakeLists.txt file, CMake scans that file’s
contents looking for a call to project().
If no such call is found, CMake will issue a warning and insert an internal
call to project() with the default C and C++ languages enabled.
Projects should never rely on this mechanism, they should always explicitly
call project() themselves.
Note that it is not enough to call project() through a wrapper function or
via a file read in via add_subdirectory() or include(), the top level
CMakeLists.txt file must call project() directly.
调用project()子目录通常不会造成任何损害,但可能会导致 CMake 必须生成额外的文件。在大多数情况下,这些额外的project()调用和生成的文件只是噪音,但在某些情况下它们可能很有用。使用 Visual Studio 项目生成器时,每个project()命令都会创建关联的解决方案文件。通常,开发人员会加载与最顶层project()调用相对应的解决方案文件(该解决方案文件将位于构建目录的顶部)。该顶级解决方案文件包含项目中的所有目标。为子目录中的任何调用生成的解决方案文件project()将包含更精简的视图,仅包含该目录范围及以下目录中的目标,以及它们所依赖的构建其余部分中的任何其他目标。开发人员可以加载这些子解决方案而不是顶级解决方案,以获得更精简的项目视图,从而使他们能够专注于目标集的较小子集。对于具有多个目标的大型项目,这尤其有用。
Calling project() in subdirectories typically does no harm, but it may result
in CMake having to generate extra files.
For the most part, these extra project() calls and generated files are just
noise, but in some cases they can be useful.
When using a Visual Studio project generator, each project() command
results in the creation of an associated solution file.
Normally, the developer would load the solution file corresponding to the
top-most project() call (that solution file will be at the top of the build
directory).
This top level solution file contains all the targets in the project.
The solution files generated for any project() calls within subdirectories
will contain a more trimmed down view, containing just the targets from that
directory scope and below, plus any other targets from the rest of the build
that they depend on.
Developers can load these sub-solutions instead of the top level one for a more
trimmed-down view of the project, allowing them to focus on a smaller subset of
the set of targets.
For very large projects with many targets, this can be especially useful.
Xcode 生成器的行为方式类似,为每个project()调用创建一个 Xcode 项目。这些 Xcode 项目可以加载为类似的精简视图,但与 Visual Studio 生成器不同,它们不包含从该目录范围之外或以下构建目标的逻辑。开发人员负责确保已经构建了修剪后的视图之外所需的任何内容。实际上,这意味着在切换到精简的 Xcode 项目之前,可能需要先加载和构建顶级项目。
The Xcode generator behaves in a similar way, creating an Xcode project for
each project() call.
These Xcode projects can be loaded for a similar trimmed down view, but
unlike for Visual Studio generators, they do not include the logic for building
targets from outside of that directory scope or below.
The developer is responsible for ensuring that anything required from outside
of that trimmed down view has been built already.
In practice, this means the top level project likely needs to be loaded and
built first before switching to the trimmed down Xcode project.
CMake 提供的从其他目录拉取内容的另一种方法是命令include(),它有以下两种形式:
The other method CMake provides for pulling in content from other directories
is the include() command, which has the following two forms:
include(fileName
[OPTIONAL] [RESULT_VARIABLE myVar] [NO_POLICY_SCOPE]
)
include(module
[OPTIONAL] [RESULT_VARIABLE myVar] [NO_POLICY_SCOPE]
)include(fileName
[OPTIONAL] [RESULT_VARIABLE myVar] [NO_POLICY_SCOPE]
)
include(module
[OPTIONAL] [RESULT_VARIABLE myVar] [NO_POLICY_SCOPE]
)
第一种形式有点类似于add_subdirectory(),但有重要的区别:
The first form is somewhat analogous to add_subdirectory(), but there are
important differences:
include()期望读入文件的名称,而
add_subdirectory()期望读取目录并在该目录中查找CMakeLists.txt
文件。传递给的文件名include()通常具有扩展名.cmake,但它可以是任何内容。
include() expects the name of a file to read in, whereas
add_subdirectory() expects a directory and will look for a CMakeLists.txt
file within that directory. The file name passed to include() typically
has the extension .cmake, but it can be anything.
include()不会引入新的变量作用域,而会引入新的变量
add_subdirectory()作用域。
include() does not introduce a new variable scope, whereas
add_subdirectory() does.
include()
可以使用选项告诉命令不要这样做NO_POLICY_SCOPE(add_subdirectory()没有这样的选项)。有关策略范围处理的更多详细信息,请参阅第 12 章“策略” 。
include()
command can be told not to do so with the NO_POLICY_SCOPE option
(add_subdirectory() has no such option). See Chapter 12, Policies for further
details on policy scope handling.
CMAKE_CURRENT_SOURCE_DIR在处理名为 的文件时,和变量的值CMAKE_CURRENT_BINARY_DIR
不会改变include(),但对于 ,它们会改变add_subdirectory()。稍后将对此进行更详细的讨论。
CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_BINARY_DIR
variables do not change when processing the file named by include(),
whereas they do change for add_subdirectory(). This will be discussed in
more detail shortly.
该include()命令的第二种形式具有完全不同的目的。它用于加载命名模块,这是第 11 章模块中深入讨论的主题
。除第一点外,上述所有要点也适用于第二种形式。
The second form of the include() command serves an entirely different
purpose. It is used to load the named module, a topic covered in depth in
Chapter 11, Modules. All but the first of the above points also hold true for this
second form.
由于 的值在被调用CMAKE_CURRENT_SOURCE_DIR时不会改变include()
,因此包含的文件似乎很难计算出它所在的目录。CMAKE_CURRENT_SOURCE_DIR将包含调用文件的位置include(),而不是包含包含文件的目录。此外,与始终为 的add_subdirectory()情况不同,使用 时文件的名称可以是任何名称,因此包含的文件可能很难确定其自己的名称。为了解决此类情况,CMake 提供了一组附加变量:fileNameCMakeLists.txtinclude()
Since the value of CMAKE_CURRENT_SOURCE_DIR does not change when include()
is called, it may seem difficult for the included file to work out the
directory in which it resides. CMAKE_CURRENT_SOURCE_DIR will contain the
location of the file from where include() was called, not the directory
containing the included file. Furthermore, unlike add_subdirectory() where
the fileName will always be CMakeLists.txt, the name of the file can be
anything when using include(), so it can be difficult for the included file
to determine its own name. To address situations like these, CMake provides an
additional set of variables:
CMAKE_CURRENT_LIST_DIR
CMAKE_CURRENT_LIST_DIR
CMAKE_CURRENT_SOURCE_DIR只是在处理包含的文件时它将被更新。这是需要当前正在处理的文件的目录时使用的变量,无论它是如何添加到构建中的。它将始终保持绝对路径。
CMAKE_CURRENT_SOURCE_DIR except
it will be updated when processing the included file. This is the variable to
use where the directory of the current file being processed is required, no
matter how it has been added to the build. It will always hold an absolute
path.
CMAKE_CURRENT_LIST_FILE
CMAKE_CURRENT_LIST_FILE
CMAKE_CURRENT_LIST_LINE
CMAKE_CURRENT_LIST_LINE
值得注意的是,上述三个变量适用于CMake 处理的任何include()文件,而不仅仅是那些由命令拉入的文件。CMakeLists.txt即使对于通过 拉入的文件,它们也具有与上述相同的值add_subdirectory(),在这种情况下,CMAKE_CURRENT_LIST_DIR
它们将具有与 相同的值CMAKE_CURRENT_SOURCE_DIR。以下示例演示了该行为:
It is important to note that the above three variables work for any file
being processed by CMake, not just those pulled in by an include() command.
They have the same values as described above even for a CMakeLists.txt file
pulled in via add_subdirectory(), in which case CMAKE_CURRENT_LIST_DIR
would have the same value as CMAKE_CURRENT_SOURCE_DIR. The following example
demonstrates the behavior:
add_subdirectory(subdir)
message("")
include(subdir/CMakeLists.txt)add_subdirectory(subdir)
message("")
include(subdir/CMakeLists.txt)
message("CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
message("CMAKE_CURRENT_LIST_DIR = ${CMAKE_CURRENT_LIST_DIR}")
message("CMAKE_CURRENT_LIST_FILE = ${CMAKE_CURRENT_LIST_FILE}")
message("CMAKE_CURRENT_LIST_LINE = ${CMAKE_CURRENT_LIST_LINE}")message("CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
message("CMAKE_CURRENT_LIST_DIR = ${CMAKE_CURRENT_LIST_DIR}")
message("CMAKE_CURRENT_LIST_FILE = ${CMAKE_CURRENT_LIST_FILE}")
message("CMAKE_CURRENT_LIST_LINE = ${CMAKE_CURRENT_LIST_LINE}")
这会产生如下输出:
This produces output like the following:
CMAKE_CURRENT_SOURCE_DIR = /某处/src/subdir CMAKE_CURRENT_BINARY_DIR = /某处/构建/子目录 CMAKE_CURRENT_LIST_DIR = /某处/src/subdir CMAKE_CURRENT_LIST_FILE = /somewhere/src/subdir/CMakeLists.txt CMAKE_CURRENT_LIST_LINE = 5 CMAKE_CURRENT_SOURCE_DIR = /某处/src CMAKE_CURRENT_BINARY_DIR = /某处/构建 CMAKE_CURRENT_LIST_DIR = /某处/src/subdir CMAKE_CURRENT_LIST_FILE = /somewhere/src/subdir/CMakeLists.txt CMAKE_CURRENT_LIST_LINE = 5
CMAKE_CURRENT_SOURCE_DIR = /somewhere/src/subdir CMAKE_CURRENT_BINARY_DIR = /somewhere/build/subdir CMAKE_CURRENT_LIST_DIR = /somewhere/src/subdir CMAKE_CURRENT_LIST_FILE = /somewhere/src/subdir/CMakeLists.txt CMAKE_CURRENT_LIST_LINE = 5 CMAKE_CURRENT_SOURCE_DIR = /somewhere/src CMAKE_CURRENT_BINARY_DIR = /somewhere/build CMAKE_CURRENT_LIST_DIR = /somewhere/src/subdir CMAKE_CURRENT_LIST_FILE = /somewhere/src/subdir/CMakeLists.txt CMAKE_CURRENT_LIST_LINE = 5
上面的示例还突出了该
include()命令的另一个有趣的特征。它可用于包含先前已包含在构建中的文件中的内容。如果大型、复杂项目的不同子目录都希望在项目公共区域的某个文件中使用 CMake 代码,则它们可以include()独立地使用该文件。
The above example also highlights another interesting characteristic of the
include() command. It can be used to include content from a file which has
already been included in the build previously. If different subdirectories of a
large, complex project both want to make use of CMake code in some file in a
common area of the project, they may both include() that file independently.
正如将在后面的章节中看到的,有多种场景需要构建相对于源目录或构建目录中的位置的路径。考虑一个这样的示例,其中项目需要构建驻留在其顶级源目录中的文件的路径。从第 7.1.1 节“源和二进制目录变量”来看,CMAKE_SOURCE_DIR似乎很自然,允许${CMAKE_SOURCE_DIR}/someFile使用类似的路径。但请考虑一下,如果该项目后来通过将其引入父构建而合并到另一个父项目中,会发生什么情况add_subdirectory()。它可以用作 git 子模块,也可以使用第 28 章“外部内容”和第 29 章“项目组织”中讨论的技术之一按需获取。原来项目源代码树的顶部现在是父项目源代码树中的子目录。
CMAKE_SOURCE_DIR现在指向父项目的顶部,因此文件路径将指向错误的目录。也存在类似的陷阱CMAKE_BINARY_DIR。
As will be seen in later chapters, there are various scenarios where paths
relative to a location in the source or build directory need to be constructed.
Consider one such example where a project needs to construct a path to a file
that resides in its top level source directory.
From Section 7.1.1, “Source And Binary Directory Variables”, CMAKE_SOURCE_DIR seems to be
a natural fit, allowing a path like ${CMAKE_SOURCE_DIR}/someFile to be used.
But consider what happens if that project is later incorporated into another
parent project by bringing it into the parent build via add_subdirectory().
It could be used as a git submodule or fetched on demand using one of the
techniques discussed in Chapter 28, External Content and Chapter 29, Project Organization.
What used to be the top of the original project’s source tree is now a
subdirectory within the parent project’s source tree.
CMAKE_SOURCE_DIR now points to the top of the parent project, so the file
path will be pointing to the wrong directory.
A similar trap exists for CMAKE_BINARY_DIR.
上述场景在在线教程和较旧的项目中经常遇到,但很容易避免。该project()命令设置一些变量,这些变量提供了一种更可靠的方法来定义相对于目录层次结构中的位置的路径。project()以下变量在至少被调用一次后才可用:
The above scenario is encountered surprisingly often in online tutorials and
older projects, but it can be easily avoided.
The project() command sets some variables that provide a much more robust
way of defining paths relative to locations in the directory hierarchy.
The following variables will be available after project() has been called at
least once:
PROJECT_SOURCE_DIR
PROJECT_SOURCE_DIR
project()当前作用域或任何父作用域中最近调用的源目录。项目名称(即提供给project()
命令的第一个参数)不相关。
project() in the current
scope or any parent scope.
The name of the project (i.e. the first argument given to the project()
command) is not relevant.
PROJECT_BINARY_DIR
PROJECT_BINARY_DIR
PROJECT_SOURCE_DIR。
PROJECT_SOURCE_DIR.
projectName_SOURCE_DIR
projectName_SOURCE_DIR
project(projectName)当前作用域或任何父作用域中最近调用的源目录。不同的是PROJECT_SOURCE_DIR,它与特定的项目名称相关,因此与特定的调用相关project()。
project(projectName) in the
current scope or any parent scope.
Unlike PROJECT_SOURCE_DIR, this is tied to a specific project name and
therefore to a particular call to project().
projectName_BINARY_DIR
projectName_BINARY_DIR
projectName_SOURCE_DIR。
projectName_SOURCE_DIR.
以下示例演示了如何使用这些变量(这些
…_BINARY_DIR变量遵循与所示变量类似的模式…_SOURCE_DIR
)。
The following example demonstrates how these variables can be used (the
…_BINARY_DIR variables follow a similar pattern to the …_SOURCE_DIR
variables shown).
cmake_minimum_required(VERSION 3.0)
project(topLevel)
message("Top level:")
message(" PROJECT_SOURCE_DIR = ${PROJECT_SOURCE_DIR}")
message(" topLevel_SOURCE_DIR = ${topLevel_SOURCE_DIR}")
add_subdirectory(child)cmake_minimum_required(VERSION 3.0)
project(topLevel)
message("Top level:")
message(" PROJECT_SOURCE_DIR = ${PROJECT_SOURCE_DIR}")
message(" topLevel_SOURCE_DIR = ${topLevel_SOURCE_DIR}")
add_subdirectory(child)
message("Child:")
message(" PROJECT_SOURCE_DIR (before) = ${PROJECT_SOURCE_DIR}")
project(child)
message(" PROJECT_SOURCE_DIR (after) = ${PROJECT_SOURCE_DIR}")
message(" child_SOURCE_DIR = ${child_SOURCE_DIR}")
add_subdirectory(grandchild)message("Child:")
message(" PROJECT_SOURCE_DIR (before) = ${PROJECT_SOURCE_DIR}")
project(child)
message(" PROJECT_SOURCE_DIR (after) = ${PROJECT_SOURCE_DIR}")
message(" child_SOURCE_DIR = ${child_SOURCE_DIR}")
add_subdirectory(grandchild)
message("Grandchild:")
message(" PROJECT_SOURCE_DIR = ${PROJECT_SOURCE_DIR}")
message(" child_SOURCE_DIR = ${child_SOURCE_DIR}")
message(" topLevel_SOURCE_DIR = ${topLevel_SOURCE_DIR}")message("Grandchild:")
message(" PROJECT_SOURCE_DIR = ${PROJECT_SOURCE_DIR}")
message(" child_SOURCE_DIR = ${child_SOURCE_DIR}")
message(" topLevel_SOURCE_DIR = ${topLevel_SOURCE_DIR}")
cmake在此项目层次结构的顶层运行将提供类似于以下内容的输出:
Running cmake on the top level of this project hierarchy would give output
similar to the following:
顶层: PROJECT_SOURCE_DIR = /某处/src topLevel_SOURCE_DIR = /某处/src 孩子: PROJECT_SOURCE_DIR(之前)= /somewhere/src PROJECT_SOURCE_DIR(之后)= /somewhere/src/child child_SOURCE_DIR = /某处/src/child 孙子: PROJECT_SOURCE_DIR = /某处/src/child child_SOURCE_DIR = /某处/src/child topLevel_SOURCE_DIR = /某处/src
Top level: PROJECT_SOURCE_DIR = /somewhere/src topLevel_SOURCE_DIR = /somewhere/src Child: PROJECT_SOURCE_DIR (before) = /somewhere/src PROJECT_SOURCE_DIR (after) = /somewhere/src/child child_SOURCE_DIR = /somewhere/src/child Grandchild: PROJECT_SOURCE_DIR = /somewhere/src/child child_SOURCE_DIR = /somewhere/src/child topLevel_SOURCE_DIR = /somewhere/src
上面的例子显示了项目相关变量的多功能性。可以从目录层次结构的任何部分使用它们来可靠地引用项目中的任何其他目录。对于本节开头讨论的场景,使用
${PROJECT_SOURCE_DIR}/someFile或${projectName_SOURCE_DIR}/someFile
代替${CMAKE_SOURCE_DIR}/someFile将确保路径
someFile正确,无论项目是独立构建还是合并到更大的项目层次结构中。
The above example shows the versatility of the project-related variables.
They can be used from any part of the directory hierarchy to robustly refer to
any other directory in the project.
For the scenario discussed at the start of this section, using
${PROJECT_SOURCE_DIR}/someFile or perhaps ${projectName_SOURCE_DIR}/someFile
instead of ${CMAKE_SOURCE_DIR}/someFile would ensure that the path to
someFile would be correct, regardless of whether the project is being built
stand-alone or being incorporated into a larger project hierarchy.
一些分层构建安排允许项目独立构建或作为较大父项目的一部分构建(请参见第 28.2 节“FetchContent”)。项目的某些部分可能只有在构建的顶部才有意义,例如设置打包支持。CMAKE_SOURCE_DIR项目可以通过比较和的值来检测是否是顶级
CMAKE_CURRENT_SOURCE_DIR。如果它们相同,则当前目录范围必须是源代码树的顶层。
Some hierarchical build arrangements allow a project to be built either
stand-alone or as part of a larger parent project (see Section 28.2, “FetchContent”).
Some parts of the project might only make sense if it is the top of the build,
such as setting up packaging support.
A project can detect whether it is the top level by comparing the value of
CMAKE_SOURCE_DIR with CMAKE_CURRENT_SOURCE_DIR.
If they are the same, then the current directory scope must be the top level of
the source tree.
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
add_subdirectory(packaging)
endif()if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
add_subdirectory(packaging)
endif()
所有版本的 CMake 都支持上述技术,并且是一种非常常见的模式。在 CMake 3.21 或更高版本中,PROJECT_IS_TOP_LEVEL提供了一个专用变量,它可以实现相同的结果,但其意图更清晰:
The above technique is supported by all versions of CMake and is a very common
pattern.
With CMake 3.21 or later, a dedicated PROJECT_IS_TOP_LEVEL variable is
provided which can achieve the same result, but is clearer in its intent:
# Requires CMake 3.21 or later
if(PROJECT_IS_TOP_LEVEL)
add_subdirectory(packaging)
endif()# Requires CMake 3.21 or later
if(PROJECT_IS_TOP_LEVEL)
add_subdirectory(packaging)
endif()
PROJECT_IS_TOP_LEVEL如果当前目录范围或以上目录中的最新调用
project()位于顶级
文件中,则的值将为 true CMakeLists.txt。<projectName>_IS_TOP_LEVELCMake 3.21 或更高版本还为每次调用 定义了类似的变量project()。它被创建为缓存变量,因此可以从任何目录范围读取。
对应于感兴趣的命令<projectName>的名称。当当前范围和感兴趣的项目范围之间project()可能存在中间调用时,此替代变量非常有用
。project()
The value of PROJECT_IS_TOP_LEVEL will be true if the most recent call to
project() in the current directory scope or above was in the top level
CMakeLists.txt file.
A similar variable, <projectName>_IS_TOP_LEVEL, is also defined by
CMake 3.21 or later for every call to project().
It is created as a cache variable, so it can be read from any directory scope.
<projectName> corresponds to the name given to the project() command of
interest.
This alternative variable is useful when there may be intervening calls to
project() between the current scope and the scope of the project of
interest.
有时,项目可能希望停止处理当前文件的其余部分并将控制权返回给调用者。该
return()命令正好可以用于此目的,但请注意它不能将值返回给调用者。它的唯一作用是结束当前作用域的处理。如果不是从函数内部调用,return()则结束当前文件的处理,无论它是通过
include()或引入的add_subdirectory()。第 8.4 节“范围”return()介绍了函数内部调用的影响,包括对可能导致无意中从当前文件返回的常见错误的特别注意。
There can be occasions where a project may want to stop processing the
remainder of the current file and return control back to the caller. The
return() command can be used for exactly this purpose, but note that it
cannot return a value to the caller. Its only effect is to end processing of
the current scope. If not called from inside a function, return() ends
processing of the current file regardless of whether it was brought in via
include() or add_subdirectory(). The effect of calling return() inside a
function is covered in Section 8.4, “Scope”, including special attention for a
common mistake that can result in returning from the current file
unintentionally.
如上一节所述,项目的不同部分可能包含来自多个位置的相同文件。有时可能需要检查这一点并仅包含该文件一次,并提前返回以进行后续包含,以防止多次重新处理该文件。这与 C/C++ 标头的情况非常相似,并且使用类似形式的包含防护是相当常见的:
As noted in the previous section, different parts of a project may include the same file from multiple places. It can sometimes be desirable to check for this and only include the file once, returning early for subsequent inclusions to prevent reprocessing the file multiple times. This is very similar to the situation for C/C++ headers and it is fairly common to see a similar form of include guard used:
if(DEFINED cool_stuff_include_guard)
return()
endif()
set(cool_stuff_include_guard 1)
# ...if(DEFINED cool_stuff_include_guard)
return()
endif()
set(cool_stuff_include_guard 1)
# ...
使用 CMake 3.10 或更高版本,可以使用专用命令更简洁、更稳健地表达这一点,其行为类似于#pragma onceC/C++ 的:
With CMake 3.10 or later, this can be expressed more succinctly and robustly
with a dedicated command whose behavior is analogous to the #pragma once of
C/C++:
include_guard()include_guard()
与手动编写if-endif代码相比,这更加健壮,因为它在内部处理保护变量的名称。该命令还接受可选的关键字参数DIRECTORY或GLOBAL指定不同的范围来检查先前已处理的文件,但在大多数情况下不太可能需要这些关键字。如果没有指定任何参数,则假定变量作用域,并且效果与上面的代码完全相同if-endif。GLOBAL确保命令结束文件的处理(如果该文件在项目中的其他任何地方之前已被处理过)(即变量范围被忽略)。DIRECTORY仅检查当前目录范围内及以下目录范围内的先前处理。
Compared to manually writing out the if-endif code, this is more robust
because it handles the name of the guard variable internally. The command also
accepts an optional keyword argument DIRECTORY or GLOBAL to specify a
different scope within which to check for the file having been processed
previously, but these keywords are unlikely to be needed in most situations.
With neither argument specified, variable scope is assumed and the effect is
exactly equivalent to the if-endif code above. GLOBAL ensures the command
ends processing of the file if it has been processed before anywhere else in
the project (i.e. variable scope is ignored). DIRECTORY checks for previous
processing only within the current directory scope and below.
add_subdirectory()使用或include()将另一个目录引入构建之间的最佳选择并不总是显而易见的。一方面,
add_subdirectory()更简单并且可以更好地保持目录相对独立,因为它创建了自己的范围。另一方面,某些 CMake 命令具有限制,仅允许它们对当前文件范围内定义的内容进行操作,因此include()在这些情况下效果更好。第 29.5.1 节“目标源”讨论了本主题的一些方面。
The best choice between using add_subdirectory() or include() to bring
another directory into the build is not always obvious. On the one hand,
add_subdirectory() is simpler and does a better job of keeping directories
relatively self contained because it creates its own scope. On the other, some
CMake commands have restrictions which only allow them to operate on things
defined within the current file scope, so include() works better for those
cases. Section 29.5.1, “Target Sources” discusses some aspects of this topic.
作为一般指南,大多数简单的项目可能最好选择add_subdirectory()使用include(). 它促进了项目的更清晰的定义,并允许CMakeLists.txt
给定目录更多地关注该目录需要定义的内容。遵循这一策略将促进整个项目中信息的更好本地化,并且也倾向于仅在需要和带来有用好处的地方引入复杂性。这并不是说它include()本身比 更复杂
add_subdirectory(),而是使用include()往往会导致需要更明确地拼写文件路径,因为 CMake 认为当前源目录不是包含文件的目录。在最近的 CMake 版本中,与从不同目录调用某些命令相关的许多限制也已被删除,这进一步强化了偏好的论点add_subdirectory()。
As a general guide, most simple projects are probably better off preferring to
use add_subdirectory() over include().
It promotes cleaner definition of the project and allows the CMakeLists.txt
for a given directory to focus more on just what that directory needs to define.
Following this strategy will promote better locality of information throughout
the project and will also tend to introduce complexity only where it is needed
and where it brings useful benefits.
It’s not that include() itself is any more complicated than
add_subdirectory(), but the use of include() tends to result in paths to
files needing to be more explicitly spelled out, since what CMake considers the
current source directory is not that of the included file.
Many of the restrictions associated with calling certain commands from
different directories have been removed in more recent CMake versions too,
which further strengthens the argument to prefer add_subdirectory().
无论是否使用add_subdirectory()或include()两者的组合,该CMAKE_CURRENT_LIST_DIR变量通常都是比 更好的选择CMAKE_CURRENT_SOURCE_DIR。通过养成尽早使用的习惯,随着项目复杂性的增加,在和CMAKE_CURRENT_LIST_DIR之间进行切换以及移动整个目录来重组项目会变得更加容易。add_subdirectory()include()
Irrespective of whether using add_subdirectory(), include() or a
combination of both, the CMAKE_CURRENT_LIST_DIR variable is generally going
to be a better choice than CMAKE_CURRENT_SOURCE_DIR. By establishing the
habit of using CMAKE_CURRENT_LIST_DIR early, it is much easier to switch
between add_subdirectory() and include() as a project grows in complexity
and to move entire directories to restructure a project.
如果可能,请避免使用CMAKE_SOURCE_DIR和CMAKE_BINARY_DIR
变量,因为这些通常会破坏项目合并到更大项目层次结构中的能力。在绝大多数情况下,PROJECT_SOURCE_DIR和PROJECT_BINARY_DIR,或它们特定于项目的等效项projectName_SOURCE_DIR和
projectName_BINARY_DIR是更合适使用的变量。
Where possible, avoid using the CMAKE_SOURCE_DIR and CMAKE_BINARY_DIR
variables, as these typically break the ability of the project to be
incorporated into a larger project hierarchy.
In the vast majority of cases, PROJECT_SOURCE_DIR and PROJECT_BINARY_DIR,
or their project-specific equivalents projectName_SOURCE_DIR and
projectName_BINARY_DIR are more appropriate variables to use.
如果项目需要 CMake 3.10 或更高版本,则
在必须防止多次包含文件的情况下,最好使用include_guard()不带参数的命令,而不是显式块。if-endif
If the project requires CMake 3.10 or later, prefer to use the
include_guard() command without arguments instead of an explicit if-endif
block in cases where multiple inclusion of a file must be prevented.
project()避免任意调用每个子目录的做法CMakeLists.txt
。仅
当子目录可以被视为或多或少独立的项目时,才考虑将project()命令放入子目录的文件中。CMakeLists.txt除非整个构建具有非常多的目标,否则几乎不需要调用project()顶级CMakeLists.txt文件之外的任何地方。
Avoid the practice of arbitrarily calling project() in the CMakeLists.txt
of every subdirectory.
Only consider putting a project() command in a subdirectory’s CMakeLists.txt
file if that subdirectory can be treated as a more or less standalone project.
Unless the whole build has a very high number of targets, there is little need
to call project() anywhere other than in the top level CMakeLists.txt file.
回顾到目前为止本书所涵盖的内容,CMake 的语法本身已经开始看起来很像一种编程语言。它支持变量、if-then-else 逻辑、循环和包含其他要处理的文件。得知 CMake 还支持函数和宏的常见编程概念也就不足为奇了。与它们在其他编程语言中的作用非常相似,函数和宏是项目和开发人员扩展 CMake 功能并以自然方式封装重复任务的主要机制。它们允许开发人员定义可重用的 CMake 代码块,这些代码块可以像常规内置 CMake 命令一样调用。它们也是 CMake 自己的模块系统的基石(在第 11 章“模块”中单独介绍)。
Looking back on the material covered in this book so far, CMake’s syntax is already starting to look a lot like a programming language in its own right. It supports variables, if-then-else logic, looping and inclusion of other files to be processed. It should be no surprise to learn that CMake also supports the common programming concepts of functions and macros too. Much like their role in other programming languages, functions and macros are the primary mechanism for projects and developers to extend CMake’s functionality and to encapsulate repetitive tasks in a natural way. They allow the developer to define reusable blocks of CMake code which can be called just like regular built-in CMake commands. They are also a cornerstone of CMake’s own module system (covered separately in Chapter 11, Modules).
CMake 中的函数和宏与 C/C++ 中的同名函数和宏具有非常相似的特征。函数引入了新的作用域,函数参数成为函数体内可访问的变量。另一方面,宏有效地将其主体粘贴到调用点,并且宏参数被替换为简单的字符串替换。这些行为反映了函数和宏在 C/C++ 中的工作方式#define。CMake 函数或宏定义如下:
Functions and macros in CMake have very similar characteristics to their
same-named counterparts in C/C++. Functions introduce a new scope and the
function arguments become variables accessible inside the function body. Macros,
on the other hand, effectively paste their body into the point of the call
and the macro arguments are substituted as simple string replacements. These
behaviors mirror the way functions and #define macros work in C/C++.
A CMake function or macro is defined as follows:
function(name [arg1 [arg2 [...]]])
# Function body (i.e. commands) ...
endfunction()
macro(name [arg1 [arg2 [...]]])
# Macro body (i.e. commands) ...
endmacro()function(name [arg1 [arg2 [...]]])
# Function body (i.e. commands) ...
endfunction()
macro(name [arg1 [arg2 [...]]])
# Macro body (i.e. commands) ...
endmacro()
定义后,函数或宏的调用方式与任何其他 CMake 命令完全相同。然后函数或宏的主体在调用时执行。例如:
Once defined, the function or macro is called in exactly the same way as any other CMake command. The function or macro’s body is then executed at the point of the call. For example:
function(print_me)
message("Hello from inside a function")
message("All done")
endfunction()
# Called like so:
print_me()function(print_me)
message("Hello from inside a function")
message("All done")
endfunction()
# Called like so:
print_me()
如上所示,name参数定义了用于调用函数或宏的名称,它只能包含字母、数字和下划线。名称将不区分大小写,因此大写/小写约定更多的是风格问题(CMake 文档遵循命令名称全部小写且单词之间用下划线分隔的约定)。CMake 的早期版本要求将重复作为或name的参数
,但新项目应该避免这种情况,因为它只会增加不必要的混乱。endfunction()endmacro()
As shown above, the name argument defines the name used to call the function
or macro and it should only contain letters, numbers and underscores. The name
will be treated case-insensitively, so upper/lowercase conventions are more a
matter of style (the CMake documentation follows the convention that command
names are all lowercase with words separated by underscores). Very early
versions of CMake required the name to be repeated as an argument to
endfunction() or endmacro(), but new projects should avoid this as it only
adds unnecessary clutter.
除了一个非常重要的区别之外,函数和宏的参数处理是相同的。对于函数,每个参数都是一个 CMake 变量,并且具有 CMake 变量的所有常见行为。例如,它们可以在if()语句中作为变量进行测试。相比之下,宏参数是字符串替换,因此用作宏调用参数的任何内容本质上都会粘贴到该参数出现在宏体中的任何位置。如果在语句中使用宏参数if(),它将被视为字符串而不是变量。以下示例及其输出演示了其中的差异:
The argument handling of functions and macros is the same except for one very
important difference. For functions, each argument is a CMake variable and has
all the usual behaviors of a CMake variable. For example, they can be tested
in if() statements as variables. In comparison, macro arguments are string
replacements, so whatever was used as the argument to the macro call is
essentially pasted into wherever that argument appears in the macro
body. If a macro argument is used in an if() statement, it would be treated
as a string rather than a variable. The following example and its output
demonstrate the difference:
function(func arg)
if(DEFINED arg)
message("Function arg is a defined variable")
else()
message("Function arg is NOT a defined variable")
endif()
endfunction()
macro(macr arg)
if(DEFINED arg)
message("Macro arg is a defined variable")
else()
message("Macro arg is NOT a defined variable")
endif()
endmacro()
func(foobar)
macr(foobar)function(func arg)
if(DEFINED arg)
message("Function arg is a defined variable")
else()
message("Function arg is NOT a defined variable")
endif()
endfunction()
macro(macr arg)
if(DEFINED arg)
message("Macro arg is a defined variable")
else()
message("Macro arg is NOT a defined variable")
endif()
endmacro()
func(foobar)
macr(foobar)
函数arg是一个定义的变量 宏 arg 不是已定义的变量
Function arg is a defined variable Macro arg is NOT a defined variable
除了这种差异之外,函数和宏在参数处理方面都支持相同的功能。函数定义中的每个参数都充当它所代表的参数的区分大小写的标签。对于函数,该标签的作用类似于变量,而对于宏,它的作用类似于字符串替换。即使宏参数在技术上不是变量,也可以使用通常的变量表示法在函数或宏体中访问该参数的值。
Aside from that difference, functions and macros both support the same features when it comes to argument processing. Each argument in the function definition serves as a case-sensitive label for the argument it represents. For functions, that label acts like a variable, whereas for macros it acts like a string substitution. The value of that argument can be accessed in the function or macro body using the usual variable notation, even though macro arguments are not technically variables.
function(func myArg)
message("myArg = ${myArg}")
endfunction()
macro(macr myArg)
message("myArg = ${myArg}")
endmacro()
func(foobar)
macr(foobar)function(func myArg)
message("myArg = ${myArg}")
endfunction()
macro(macr myArg)
message("myArg = ${myArg}")
endmacro()
func(foobar)
macr(foobar)
调用func()和打印macr()相同的内容:
Both the call to func() and the call to macr() print the same thing:
myArg = foobar
myArg = foobar
除了命名参数之外,函数和宏还附带一组自动定义的变量(在宏的情况下是类似变量的名称),这些变量允许除了命名参数之外或代替命名参数处理参数:
In addition to the named arguments, functions and macros come with a set of automatically defined variables (or variable-like names in the case of macros) which allow processing of arguments in addition to or instead of the named ones:
ARGC
ARGC
ARGV
ARGV
ARGN
ARGN
ARGV,只不过它仅包含命名参数之外的参数(即可选的未命名参数)。
ARGV, except this only contains arguments beyond the named ones
(i.e. the optional, unnamed arguments).
除了上述内容之外,还可以使用以下形式的名称来引用每个单独的参数,ARGVx其中x是参数的编号(例如ARGV0、
ARGV1等)。这包括命名参数,因此第一个命名参数也可以通过 等引用。请注意,与 一起
ARGV0使用应被视为未定义行为。ARGVxx >= ARGC
In addition to the above, each individual argument can be referenced with a
name of the form ARGVx where x is the number of the argument (e.g. ARGV0,
ARGV1, etc.). This includes the named arguments, so the first named argument
could also be referenced via ARGV0, etc.
Note that it should be considered undefined behavior to use ARGVx with
x >= ARGC.
使用名称的典型情况ARG…包括支持可选参数和实现可以处理任意数量的项目的命令。考虑一个定义可执行目标的函数,将该目标链接到某个库并为其定义测试用例。在编写测试用例时经常会遇到这样的函数(第 25 章“测试”中介绍的主题)。该函数允许定义一次步骤,然后每个测试用例就变成一个简单的一行定义,而不是为每个测试用例重复这些步骤。
Typical situations where the ARG… names are used include supporting
optional arguments and implementing a command which can take an arbitrary number
of items to be processed. Consider a function that defines an executable
target, links that target to some library and defines a test case for it.
Such a function is frequently encountered when writing test cases (a topic
covered in Chapter 25, Testing). Rather than repeating the steps for every test case,
the function allows the steps to be defined once and then each test case
becomes a simple one-line definition.
# Use a named argument for the target and treat all other
# (unnamed) arguments as the source files for the test case
function(add_mytest targetName)
add_executable(${targetName} ${ARGN})
target_link_libraries(${targetName} PRIVATE foobar)
add_test(NAME ${targetName}
COMMAND ${targetName})
endfunction()
# Define some test cases using the above function
add_mytest(smallTest small.cpp)
add_mytest(bigTest big.cpp algo.cpp net.cpp)# Use a named argument for the target and treat all other
# (unnamed) arguments as the source files for the test case
function(add_mytest targetName)
add_executable(${targetName} ${ARGN})
target_link_libraries(${targetName} PRIVATE foobar)
add_test(NAME ${targetName}
COMMAND ${targetName})
endfunction()
# Define some test cases using the above function
add_mytest(smallTest small.cpp)
add_mytest(bigTest big.cpp algo.cpp net.cpp)
ARGN上面的例子特别显示了 的用处。它允许函数或宏采用不同数量的参数,但仍指定一组必须提供的命名参数。但是,需要注意一种可能导致意外行为的特定情况。因为宏将它们的参数视为字符串替换而不是变量,所以如果它们ARGN在需要变量名称的地方使用,则它将引用的变量将在调用宏的范围内,而不是在
ARGN宏自己的范围内论据。以下示例突出显示了这种情况:
The above example shows the usefulness of ARGN in particular.
It allows a function or macro to take a varying number of arguments, yet still
specify a set of named arguments which must be provided. There is, however, a
specific case to be aware of which can result in unexpected behavior. Because
macros treat their arguments as string substitutions rather than as variables,
if they use ARGN in a place where a variable name is expected, the variable
it will refer to will be in the scope from which the macro is called, not the
ARGN from the macro’s own arguments. The following example highlights the
situation:
# WARNING: This macro is misleading
macro(dangerous)
# Which ARGN?
foreach(arg IN LISTS ARGN)
message("Argument: ${arg}")
endforeach()
endmacro()
function(func)
dangerous(1 2)
endfunction()
func(3)# WARNING: This macro is misleading
macro(dangerous)
# Which ARGN?
foreach(arg IN LISTS ARGN)
message("Argument: ${arg}")
endforeach()
endmacro()
function(func)
dangerous(1 2)
endfunction()
func(3)
上面的输出将是:
The output from the above would be:
参数:3
Argument: 3
使用LISTS关键字 with时foreach(),必须给出变量名,但ARGN为宏提供的不是变量名。当从另一个函数内部调用宏时,宏最终使用
该封闭函数中的ARGN 变量ARGN,而不是宏本身的变量。当将宏体的内容直接粘贴到调用它的函数中时(这实际上是 CMake 将对其执行的操作),情况就会变得清晰:
When using the LISTS keyword with foreach(), a variable name has to be
given, but the ARGN provided for a macro is not a variable name. When the
macro is called from inside another function, the macro ends up using the
ARGN variable from that enclosing function, not the ARGN from the macro
itself. The situation becomes clear when pasting the contents of the macro body
directly into the function where it is called (which is effectively what CMake
will do with it):
function(func)
# Now it is clear, ARGN here will use the arguments
# from func
foreach(arg IN LISTS ARGN)
message("Argument: ${arg}")
endforeach()
endfunction()function(func)
# Now it is clear, ARGN here will use the arguments
# from func
foreach(arg IN LISTS ARGN)
message("Argument: ${arg}")
endforeach()
endfunction()
在这种情况下,请考虑将宏改为函数,或者如果它必须保留为宏,则避免将参数视为变量。对于上面的示例,可以将 的实现dangerous()改为 use
foreach(arg IN ITEMS ${ARGN}),但请参阅
第 8.8 节“参数处理问题”以了解一些潜在的注意事项。
In such cases, consider making the macro a function instead, or if it must
remain a macro then avoid treating arguments as variables. For the above
example, the implementation of dangerous() could be changed to use
foreach(arg IN ITEMS ${ARGN}) instead, but see
Section 8.8, “Problems With Argument Handling” for some potential caveats.
上一节说明了如何ARG…使用变量来处理一组不同的参数。该功能对于仅需要一组可变或可选参数的简单情况来说是足够的,但如果必须支持多个可选或可变参数集,则处理变得相当乏味。此外,与许多支持基于关键字的参数和灵活的参数排序的 CMake 自己的内置命令相比,上述基本参数处理相当严格。
The previous section illustrated how the ARG… variables can be used to
handle a varying set of arguments. That functionality is sufficient for the
simple case where only one set of varying or optional arguments is needed, but
if multiple optional or variable sets of arguments must be supported, the
processing becomes quite tedious. Furthermore, the basic argument handling
described above is quite rigid compared to many of CMake’s own built-in
commands which support keyword-based arguments and flexible argument ordering.
考虑以下target_link_libraries()命令:
Consider the target_link_libraries() command:
target_link_libraries(targetName
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)target_link_libraries(targetName
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)
需要targetName作为第一个参数,但之后,调用者可以以任何顺序提供任意数量的PRIVATE,PUBLIC或INTERFACE部分,每个部分允许包含任意数量的项目。用户定义的函数和宏可以通过使用命令来支持类似级别的灵活性
cmake_parse_arguments(),命令有两种形式。所有 CMake 版本都支持第一种形式,并且适用于函数和宏:
The targetName is required as the first argument, but after that, callers can
provide any number of PRIVATE, PUBLIC or INTERFACE sections in any order,
with each section permitted to contain any number of items. User-defined
functions and macros can support a similar level of flexibility by using the
cmake_parse_arguments() command, of which there are two forms.
The first form is supported by all CMake versions and works for both functions
and macros:
# Needed only for CMake 3.4 and earlier
include(CMakeParseArguments)
cmake_parse_arguments(
prefix
valuelessKeywords singleValueKeywords multiValueKeywords
argsToParse...
)# Needed only for CMake 3.4 and earlier
include(CMakeParseArguments)
cmake_parse_arguments(
prefix
valuelessKeywords singleValueKeywords multiValueKeywords
argsToParse...
)
该cmake_parse_arguments()命令以前由模块提供
CMakeParseArguments,但在 CMake 3.5 中成为内置命令。该include(CMakeParseArguments)行在 CMake 3.5 及更高版本中不会执行任何操作,而对于早期版本的 CMake,它将定义命令
cmake_parse_arguments()(有关 的此类用法的更多信息,请参阅第 11 章,模块include())。
The cmake_parse_arguments() command used to be provided by the
CMakeParseArguments module, but it became a built-in command in CMake 3.5.
The include(CMakeParseArguments) line will do nothing in CMake 3.5 and later,
while for earlier versions of CMake it will define the
cmake_parse_arguments() command (see Chapter 11, Modules for more on this
sort of usage of include()).
第二种形式是在 CMake 3.7 中引入的,只能在函数中使用,不能在宏中使用:
The second form was introduced in CMake 3.7 and can only be used in functions, not macros:
# Available with CMake 3.7 or later, do not use in macros
cmake_parse_arguments(
PARSE_ARGV startIndex
prefix
valuelessKeywords singleValueKeywords multiValueKeywords
)# Available with CMake 3.7 or later, do not use in macros
cmake_parse_arguments(
PARSE_ARGV startIndex
prefix
valuelessKeywords singleValueKeywords multiValueKeywords
)
该命令的两种形式都很相似,唯一的区别在于它们接受要解析的参数集的方式。对于第一种形式,argsToParse通常${ARGN}不带引号。这提供了除了命名参数之外赋予封闭函数或宏的所有参数,除了一些在大多数情况下不适用的特定极端情况(请参见第 8.8 节“参数处理问题”)。
Both forms of the command are similar, differing only in the way they take in
the set of arguments to parse.
With the first form, argsToParse will typically be given as ${ARGN} without
quotes.
This provides all arguments given to the enclosing function or macro beyond the
named arguments, except for a few specific corner cases that don’t apply in
most situations (see Section 8.8, “Problems With Argument Handling”).
在第二种形式中,该PARSE_ARGV选项指示cmake_parse_arguments()直接从变量集中读取参数${ARGVx},x
范围从startIndex到(ARGC - 1)。因为它直接读取变量,所以不支持在宏内部使用。正如第 8.2 节“参数处理要点”中已经解释的那样,宏使用字符串替换而不是变量作为其参数。第二种形式的主要优点是,对于函数来说,它可以稳健地处理第一种形式无法处理的极端情况。如果封闭函数没有命名参数,则在
没有任何极端情况适用时,将${ARGV}or传递${ARGN}给第一种形式相当于传递
给第二种形式。PARSE_ARGV 0
In the second form, the PARSE_ARGV option tells cmake_parse_arguments() to
read the arguments directly from the set of ${ARGVx} variables, with x
ranging from startIndex to (ARGC - 1).
Because it reads variables directly, it does not support being used inside
macros.
As already explained in Section 8.2, “Argument Handling Essentials”, macros use string
replacement rather than variables for its arguments.
The main advantage of the second form is that for functions, it robustly
handles the corner cases that the first form does not.
If there are no named arguments for the enclosing function, then passing
${ARGV} or ${ARGN} to the first form is equivalent to giving
PARSE_ARGV 0 to the second form when none of the corner cases apply.
这两种形式的命令的其余行为是相同的。每个都是…Keywords在解析期间要搜索的关键字名称列表。因为它们是一个列表,所以需要用引号括起来以确保正确处理它们。定义valuelessKeywords独立的关键字参数,其作用类似于布尔开关。关键字存在意味着一回事,不存在则意味着另一回事。singleValueKeywords使用时,每个都需要在关键字后添加一个附加参数,而在multiValueKeywords使用关键字后则需要零个或多个附加参数。虽然不是必需的,但普遍的惯例是关键字全部大写,如果需要,单词之间用下划线分隔。请注意,关键字不应太长,否则使用起来很麻烦。
The rest of the behavior of the two forms of the command is the same.
Each of the …Keywords is a list of keyword names to search for during
parsing.
Because they are a list, they need to be surrounded by quotes to ensure they
are handled correctly.
The valuelessKeywords define standalone keyword arguments which act like
boolean switches.
The keyword being present means one thing, its absence another.
The singleValueKeywords each require exactly one additional argument after
the keyword when they are used, whereas multiValueKeywords require zero or
more additional arguments after the keyword.
While not required, the prevailing convention is for keywords to be all
uppercase, with words separated by underscores if required.
Note that keywords should not be too long or they can be cumbersome to use.
返回时cmake_parse_arguments(),可以定义变量,其名称由指定的prefix、下划线和与其关联的关键字的名称组成。例如,如果前缀为ARG,则与名为 的关键字对应的变量FOO将为ARG_FOO。对于每个,如果关键字存在或不存在,valuelessKeywords则将使用该值定义相应的变量。对于每个和,仅当该关键字存在并且在关键字后提供了值时才会定义相应的变量。一个示例最好地说明了如何定义和处理三种不同的关键字类型:TRUEFALSEsingleValueKeywordsmultiValueKeywords
When cmake_parse_arguments() returns, variables may be defined whose names
consist of the specified prefix, an underscore and the name of the keyword
they are associated with. For example, with a prefix of ARG, the
variable corresponding to a keyword named FOO would be ARG_FOO.
For each of the valuelessKeywords, the corresponding variable will be
defined with the value TRUE if the keyword is present or FALSE if it is
not.
For each of the singleValueKeywords and multiValueKeywords, the
corresponding variable will only be defined if that keyword is present and a
value is provided after the keyword.
An example best illustrates how the three different keyword types are defined
and handled:
function(func)
# Define the supported set of keywords
set(prefix ARG)
set(noValues ENABLE_NET COOL_STUFF)
set(singleValues TARGET)
set(multiValues SOURCES IMAGES)
# Process the arguments passed in
include(CMakeParseArguments)
cmake_parse_arguments(
${prefix}
"${noValues}" "${singleValues}" "${multiValues}"
${ARGN}
)
# Log details for each supported keyword
message("Option summary:")
foreach(arg IN LISTS noValues)
if(${prefix}_${arg})
message(" ${arg} enabled")
else()
message(" ${arg} disabled")
endif()
endforeach()
foreach(arg IN LISTS singleValues multiValues)
# Single argument values will print as a string
# Multiple argument values will print as a list
message(" ${arg} = ${${prefix}_${arg}}")
endforeach()
endfunction()
# Examples of calling with different combinations
# of keyword arguments
func(SOURCES foo.cpp bar.cpp
TARGET MyApp
ENABLE_NET
)
func(COOL_STUFF
TARGET dummy
IMAGES here.png there.png gone.png
)function(func)
# Define the supported set of keywords
set(prefix ARG)
set(noValues ENABLE_NET COOL_STUFF)
set(singleValues TARGET)
set(multiValues SOURCES IMAGES)
# Process the arguments passed in
include(CMakeParseArguments)
cmake_parse_arguments(
${prefix}
"${noValues}" "${singleValues}" "${multiValues}"
${ARGN}
)
# Log details for each supported keyword
message("Option summary:")
foreach(arg IN LISTS noValues)
if(${prefix}_${arg})
message(" ${arg} enabled")
else()
message(" ${arg} disabled")
endif()
endforeach()
foreach(arg IN LISTS singleValues multiValues)
# Single argument values will print as a string
# Multiple argument values will print as a list
message(" ${arg} = ${${prefix}_${arg}}")
endforeach()
endfunction()
# Examples of calling with different combinations
# of keyword arguments
func(SOURCES foo.cpp bar.cpp
TARGET MyApp
ENABLE_NET
)
func(COOL_STUFF
TARGET dummy
IMAGES here.png there.png gone.png
)
相应的输出如下所示:
The corresponding output would look like this:
选项摘要: ENABLE_NET 已启用 COOL_STUFF 已禁用 目标=我的应用程序 来源= foo.cpp;bar.cpp 图片 = 选项摘要: ENABLE_NET 已禁用 COOL_STUFF 已启用 目标 = 虚拟 来源= 图像 = 这里.png;那里.png;gone.png
Option summary: ENABLE_NET enabled COOL_STUFF disabled TARGET = MyApp SOURCES = foo.cpp;bar.cpp IMAGES = Option summary: ENABLE_NET disabled COOL_STUFF enabled TARGET = dummy SOURCES = IMAGES = here.png;there.png;gone.png
上面示例中的调用cmake_parse_arguments()也可以使用第二种形式编写,如下所示:
The call to cmake_parse_arguments() in the above example could also have been
written using the second form like so:
cmake_parse_arguments(
PARSE_ARGV 0
${prefix}
"${noValues}" "${singleValues}" "${multiValues}"
)cmake_parse_arguments(
PARSE_ARGV 0
${prefix}
"${noValues}" "${singleValues}" "${multiValues}"
)
可以向命令提供参数,以便存在不与任何关键字关联的剩余参数。该cmake_parse_arguments()命令将所有剩余参数作为变量中的列表提供<prefix>_UNPARSED_ARGUMENTS。这种形式的优点PARSE_ARGV是,如果任何未解析的参数本身就是一个列表,那么它们嵌入的分号将被转义。这保留了参数的原始结构,与其他形式的命令不同,它不保留参数的原始结构。下面的简化示例更清楚地说明了这一点:
Arguments can be given to a command such that there are leftover arguments not
associated with any keyword.
The cmake_parse_arguments() command provides all leftover arguments as a
list in the variable <prefix>_UNPARSED_ARGUMENTS.
An advantage of the PARSE_ARGV form is that if any unparsed arguments are
themselves a list, their embedded semicolons will be escaped.
This preserves the original structure of the arguments, unlike the other form
of the command which doesn’t.
The following reduced example demonstrates this more clearly:
function(demoArgs)
set(noValues "")
set(singleValues SPECIAL)
set(multiValues EXTRAS)
cmake_parse_arguments(
PARSE_ARGV 0
ARG
"${noValues}" "${singleValues}" "${multiValues}"
)
message("Left-over args: ${ARG_UNPARSED_ARGUMENTS}")
foreach(arg IN LISTS ARG_UNPARSED_ARGUMENTS)
message("${arg}")
endforeach()
endfunction()
demoArgs(burger fries "cheese;tomato" SPECIAL secretSauce)function(demoArgs)
set(noValues "")
set(singleValues SPECIAL)
set(multiValues EXTRAS)
cmake_parse_arguments(
PARSE_ARGV 0
ARG
"${noValues}" "${singleValues}" "${multiValues}"
)
message("Left-over args: ${ARG_UNPARSED_ARGUMENTS}")
foreach(arg IN LISTS ARG_UNPARSED_ARGUMENTS)
message("${arg}")
endforeach()
endfunction()
demoArgs(burger fries "cheese;tomato" SPECIAL secretSauce)
剩余参数:汉堡;薯条;奶酪\;番茄 汉堡包 薯条 奶酪;番茄
Left-over args: burger;fries;cheese\;tomato burger fries cheese;tomato
在函数内部demoArgs(),调用将定义具有值 的cmake_parse_arguments()变量。、和参数不对应于任何可识别的关键字,因此它们被视为剩余参数。如上面的输出所示,由于使用了表单,因此保留了原始列表。第 8.8.2 节“转发命令参数”中重新讨论了这一重要点。ARG_SPECIALsecretSauceburgerfriescheese;tomatocheese;tomatoPARSE_ARGV
Inside the demoArgs() function, the call to cmake_parse_arguments() will
define the variable ARG_SPECIAL with the value secretSauce.
The burger, fries and cheese;tomato arguments do not correspond to any
recognized keywords, so they are treated as leftover arguments.
As the above output shows, the original cheese;tomato list is preserved
because the PARSE_ARGV form was used.
This important point is revisited in Section 8.8.2, “Forwarding Command Arguments”.
在上面的示例中,SPECIAL关键字需要跟随一个参数。如果调用省略了该值,cmake_parse_arguments()则不会引发错误。使用 CMake 3.14 或更早版本,项目将无法检测到这种情况,但使用更高版本时,可以。使用 CMake 3.15 或更高版本,该<prefix>_KEYWORDS_MISSING_VALUES变量将填充一个列表,其中包含所有存在但后面没有任何值的单值或多值关键字。这可以通过修改前面的示例来证明:
In the above example, the SPECIAL keyword expects a single argument to follow
it.
If the call had omitted the value, cmake_parse_arguments() would not raise an
error.
With CMake 3.14 or earlier, the project would not be able to detect this
situation, but with later versions, it can.
With CMake 3.15 or later, the <prefix>_KEYWORDS_MISSING_VALUES variable will
be populated with a list containing all single- or multi-value keywords that
were present but which did not have any value following them.
This can be demonstrated by modifying the previous example:
function(demoArgs)
set(noValues "")
set(singleValues SPECIAL)
set(multiValues ORDINARY EXTRAS)
cmake_parse_arguments(
PARSE_ARGV 0
ARG
"${noValues}" "${singleValues}" "${multiValues}"
)
message("Keywords missing values: "
"${ARG_KEYWORDS_MISSING_VALUES}"
)
endfunction()
demoArgs(burger fries SPECIAL ORDINARY EXTRAS high low)function(demoArgs)
set(noValues "")
set(singleValues SPECIAL)
set(multiValues ORDINARY EXTRAS)
cmake_parse_arguments(
PARSE_ARGV 0
ARG
"${noValues}" "${singleValues}" "${multiValues}"
)
message("Keywords missing values: "
"${ARG_KEYWORDS_MISSING_VALUES}"
)
endfunction()
demoArgs(burger fries SPECIAL ORDINARY EXTRAS high low)
在上面,SPECIALandORDINARY后面紧跟着另一个关键字,因此它们没有与之关联的值。两者都可以或应该有值,因此它们都将出现在
ARG_KEYWORDS_MISSING_VALUES由 填充的变量中cmake_parse_arguments()。对于SPECIAL,这可能是一个错误,但对于ORDINARY,它可能仍然有效,因为多值关键字可以合法地没有值。因此,项目应该小心如何使用
<prefix>_KEYWORDS_MISSING_VALUES.
In the above, SPECIAL and ORDINARY are each immediately followed by another
keyword, so they have no values associated with them.
Both can or should have values, so they will both be present in the
ARG_KEYWORDS_MISSING_VALUES variable populated by cmake_parse_arguments().
In the case of SPECIAL, it is probably an error, but for ORDINARY, it may
still be valid since multi-value keywords can legitimately have no values.
Projects should therefore be careful how they make use of
<prefix>_KEYWORDS_MISSING_VALUES.
该cmake_parse_arguments()命令提供了相当大的灵活性。虽然命令的第一种形式通常${ARGN}作为要解析的参数集,但也可以给出其他参数。人们可以利用这一点来完成诸如多级参数解析之类的事情:
The cmake_parse_arguments() command provides considerable flexibility.
While the first form of the command usually takes ${ARGN} as the set of
arguments to parse, other arguments can be given.
One can take advantage of this to do things like multi-level argument parsing:
function(libWithTest)
# First level of arguments
set(groups LIB TEST)
cmake_parse_arguments(GRP
"" "" "${groups}"
${ARGN}
)
# Second level of arguments
set(args SOURCES PRIVATE_LIBS PUBLIC_LIBS)
cmake_parse_arguments(LIB
"" "TARGET" "${args}"
${GRP_LIB}
)
cmake_parse_arguments(TEST
"" "TARGET" "${args}"
${GRP_TEST}
)
add_library(${LIB_TARGET} ${LIB_SOURCES})
target_link_libraries(${LIB_TARGET}
PUBLIC ${LIB_PUBLIC_LIBS}
PRIVATE ${LIB_PRIVATE_LIBS}
)
add_executable(${TEST_TARGET} ${TEST_SOURCES})
target_link_libraries(${TEST_TARGET}
PUBLIC ${TEST_PUBLIC_LIBS}
PRIVATE ${LIB_TARGET} ${TEST_PRIVATE_LIBS}
)
endfunction()
libWithTest(
LIB
TARGET Algo
SOURCES algo.cpp algo.h
PUBLIC_LIBS SomeMathHelpers
TEST
TARGET AlgoTest
SOURCES algoTest.cpp
PRIVATE_LIBS gtest_main
)function(libWithTest)
# First level of arguments
set(groups LIB TEST)
cmake_parse_arguments(GRP
"" "" "${groups}"
${ARGN}
)
# Second level of arguments
set(args SOURCES PRIVATE_LIBS PUBLIC_LIBS)
cmake_parse_arguments(LIB
"" "TARGET" "${args}"
${GRP_LIB}
)
cmake_parse_arguments(TEST
"" "TARGET" "${args}"
${GRP_TEST}
)
add_library(${LIB_TARGET} ${LIB_SOURCES})
target_link_libraries(${LIB_TARGET}
PUBLIC ${LIB_PUBLIC_LIBS}
PRIVATE ${LIB_PRIVATE_LIBS}
)
add_executable(${TEST_TARGET} ${TEST_SOURCES})
target_link_libraries(${TEST_TARGET}
PUBLIC ${TEST_PUBLIC_LIBS}
PRIVATE ${LIB_TARGET} ${TEST_PRIVATE_LIBS}
)
endfunction()
libWithTest(
LIB
TARGET Algo
SOURCES algo.cpp algo.h
PUBLIC_LIBS SomeMathHelpers
TEST
TARGET AlgoTest
SOURCES algoTest.cpp
PRIVATE_LIBS gtest_main
)
在上面的示例中,解析的第一级参数
cmake_parse_arguments()是通常的${ARGN}. 第一级的唯一关键字是两个多值关键字LIB
和TEST。这些定义了应将其后面的子选项应用于哪个目标。第二级解析是作为${GRP_LIB}或${GRP_TEST}作为要解析的参数集而不是${ARGN}。由于子选项在原始参数集中出现多次ARGN,因此不会产生冲突,因为每个目标的子选项都是单独解析的。
In the above example, the first level of arguments parsed by
cmake_parse_arguments() is the usual ${ARGN}.
The only keywords at this first level are the two multi-value keywords LIB
and TEST.
These define which target the sub-options following it should be applied to.
The second level of parsing is fed either ${GRP_LIB} or ${GRP_TEST} as the
set of arguments to parse rather than ${ARGN}.
There is no conflict as a result of sub-options appearing more than once in the
original set of ARGN arguments, since each target’s sub-options are parsed
separately.
与使用命名参数或使用ARG…
变量的基本参数处理相比,优点cmake_parse_arguments()很多:
Compared to basic argument handling using named arguments or using the ARG…
variables, the advantages of cmake_parse_arguments() are numerous:
cmake_parse_arguments(),并且通常在函数顶部附近调用它,因此通常非常清楚该函数支持哪些参数。
cmake_parse_arguments() and it is typically called near the top of the
function, it is generally very clear what arguments the function supports.
cmake_parse_arguments()命令处理的,而不是由临时的、手动编码的解析器处理的,因此参数解析错误实际上被消除了。
cmake_parse_arguments() command rather than from an ad hoc, manually coded
parser, argument parsing bugs are virtually eliminated.
虽然这些功能非常强大,但该命令仍然有一些限制。内置命令能够支持重复关键字。例如,类似命令target_link_libraries()允许在同一命令中多次使用PRIVATE、
PUBLIC和关键字。INTERFACE该cmake_parse_arguments()命令不支持同样程度的这一点。它只会返回与最后一次出现的关键字相关的值,并丢弃较早的值。仅当使用多级关键字集时,才能重复关键字,并且该关键字在正在处理的任何给定参数集中仅出现一次。
While these capabilities are quite powerful, the command still has some
limitations.
Built-in commands are able to support keywords being repeated.
For example, commands like target_link_libraries() allow the PRIVATE,
PUBLIC and INTERFACE keywords to be used more than once in the same
command.
The cmake_parse_arguments() command does not support this to the same
extent.
It will only return the values associated with the last occurrence of a
keyword and discard the earlier ones.
A keyword can only be repeated if using a multi-level set of keywords and
the keyword only appears once in any given set of arguments being processed.
函数和宏之间的根本区别在于函数引入了新的变量范围,而宏则没有。函数从调用范围接收所有变量的副本。函数内部定义或修改的变量对函数外部的同名变量没有影响。就变量而言,函数本质上是它自己的独立沙箱,这与宏不同,宏与调用者共享相同的变量范围,因此可以直接修改调用者的变量。请注意,函数不会引入新的策略范围( 有关此问题的进一步讨论,请参阅第 12.3 节“建议实践” )。
A fundamental difference between functions and macros is that functions introduce a new variable scope, whereas macros do not. Functions receive a copy of all variables from the calling scope. Variables defined or modified inside a function have no effect on variables of the same name outside of the function. As far as variables are concerned, the function is essentially its own self-contained sandbox, unlike macros which share the same variable scope as their caller and can therefore modify the caller’s variables directly. Note that functions do not introduce a new policy scope (see Section 12.3, “Recommended Practices” for further discussion of this).
与 C/C++ 对应项不同,CMake 函数和宏不支持直接返回值。此外,由于函数引入了自己的变量范围,因此似乎没有简单的方法将信息传递回调用者,但事实并非如此。第 7.1.2 节“范围”add_subdirectory()中讨论的方法
也可用于函数。命令的关键字可用于修改调用者范围内的变量,而不是函数内的局部变量。虽然这与从函数返回值不同,但它确实允许将一个值(或多个值)传递回调用者。set()PARENT_SCOPE
Unlike their C/C++ counterparts, CMake functions and macros do not support
returning a value directly. Furthermore, since functions introduce their own
variable scope, it may seem that there is no easy way to pass information back
to the caller, but this is not the case. The same approach as was discussed for
add_subdirectory() in Section 7.1.2, “Scope” can be used for
functions too. The set() command’s PARENT_SCOPE keyword can be used to
modify a variable in the caller’s scope rather than a local variable within the
function. While this isn’t the same as returning a value from the function, it
does allow a value (or multiple values) to be passed back to the caller.
一种常见的方法是允许变量名作为函数参数传入,以便调用者仍然可以控制设置函数结果的变量的名称。这是 所使用的方法
cmake_parse_arguments(),其prefix参数确定它在调用者范围内设置的所有变量名称的前缀。以下示例及其输出演示了如何实现该技术:
A common approach is to allow a variable name to be passed in as a function
argument so that the caller is still in control of the name of variables where
function results are set. This is the approach used by
cmake_parse_arguments(), with its prefix argument determining the prefix of
all the variable names it sets in the caller’s scope. The following example and
its output demonstrate how to implement the technique:
function(func resultVar1 resultVar2)
set(${resultVar1} "First result" PARENT_SCOPE)
set(${resultVar2} "Second result" PARENT_SCOPE)
endfunction()
func(myVar otherVar)
message("myVar: ${myVar}")
message("otherVar: ${otherVar}")function(func resultVar1 resultVar2)
set(${resultVar1} "First result" PARENT_SCOPE)
set(${resultVar2} "Second result" PARENT_SCOPE)
endfunction()
func(myVar otherVar)
message("myVar: ${myVar}")
message("otherVar: ${otherVar}")
myVar:第一个结果 otherVar:第二个结果
myVar: First result otherVar: Second result
另一种选择是让函数记录它设置的变量,而不是允许调用者指定变量名称。这是不太理想的,因为它降低了函数的灵活性并为变量名称冲突提供了机会。如果可能,最好使用上述方法让调用者能够控制正在设置或修改的变量名称。
Another alternative is for a function to document the variables it sets rather than allowing the caller to specify the variable names. This is less desirable, since it reduces the flexibility of the function and opens up opportunities for variable name clashes. Where possible, it is better to use the above method to give the caller the control over variable names being set or modified.
宏的处理方式与函数相同,通过将变量作为参数传递来指定要设置的变量的名称。唯一的区别是该PARENT_SCOPE关键字不应该在宏中使用,因为它已经修改了调用者作用域中的变量。事实上,使用宏而不是函数的唯一原因是如果需要在调用范围中设置许多变量。宏将影响每次
set()调用的调用范围,而函数仅在
PARENT_SCOPE显式赋予时影响调用范围set()。
Macros can be handled the same way as functions, specifying the names of
variables to be set by passing them in as arguments. The only difference is
that the PARENT_SCOPE keyword should not be used within the macro since it
already modifies the variables in the caller’s scope. In fact, about the only
reason one would use a macro instead of a function is if many variables need to
be set in the calling scope. A macro will affect the calling scope with every
set() call, whereas a function only affects the calling scope when
PARENT_SCOPE is explicitly given to set().
在第 7.4 节“提前结束处理”中,该return()语句被讨论为在文件或函数中提前结束处理的一种方法。如上所述,return()
不返回值,它仅将处理返回到父作用域。如果
return()在函数内调用,则处理立即返回到调用者,即跳过函数的其余部分。return()
另一方面,宏内部的行为则非常不同。由于宏不会引入新的作用域,因此return()语句的行为取决于调用宏的位置。回想一下,宏有效地将其命令粘贴到调用站点。在这种情况下,return()宏中的任何语句实际上都会从称为宏的范围返回,而不是从宏本身返回。考虑以下示例:
In Section 7.4, “Ending Processing Early”, the return() statement was discussed as a way
to end processing early within a file or function. As explained above, return()
does not return a value, it only returns processing to the parent scope. If
return() is called within a function, processing returns immediately to the
caller, i.e. the rest of the function is skipped. The behavior of return()
within a macro, on the other hand, is very different. Because a macro does not
introduce a new scope, the behavior of the return() statement is dependent
on where the macro is called. Recall that a macro effectively pastes its
commands at the call site. That being the case, any return() statement from a
macro will actually be returning from the scope of whatever called the macro,
not from the macro itself. Consider the following example:
macro(inner)
message("From inner")
return() # Usually dangerous within a macro
message("Never printed")
endmacro()
function(outer)
message("From outer before calling inner")
inner()
message("Also never printed")
endfunction()
outer()macro(inner)
message("From inner")
return() # Usually dangerous within a macro
message("Never printed")
endmacro()
function(outer)
message("From outer before calling inner")
inner()
message("Also never printed")
endfunction()
outer()
上面的输出将是:
The output from the above would be:
在调用内部之前从外部开始 从内心
From outer before calling inner From inner
要强调为什么函数体中的第二条消息从不打印,请将宏体的内容粘贴到调用它的位置:
To highlight why the second message in the function body is never printed, paste the contents of the macro body into where it is called:
function(outer)
message("From outer before calling inner")
# === Pasted macro body ===
message("From inner")
return()
message("Never printed")
# === End of macro body ===
message("Also never printed")
endfunction()
outer()function(outer)
message("From outer before calling inner")
# === Pasted macro body ===
message("From inner")
return()
message("Never printed")
# === End of macro body ===
message("Also never printed")
endfunction()
outer()
现在更清楚为什么该return()语句导致处理离开函数,即使它最初是从宏内部调用的。这凸显了在宏内使用的危险return()。由于宏不创建自己的作用域,因此return()语句的结果通常不是预期的结果。
It is now much clearer why the return() statement causes processing to leave
the function, even though it was originally called from inside the macro. This
highlights the danger of using return() within macros. Because macros do not
create their own scope, the result of a return() statement is often not what
was expected.
当调用function()或macro()来定义新命令时,如果已存在具有该名称的命令,则未记录的 CMake 行为是使用相同的名称(除了前面添加下划线)使旧命令可用。无论旧名称是用于内置命令还是自定义函数或宏,这都适用。意识到这种行为的开发人员有时会试图利用它来尝试围绕现有命令创建包装器,如下所示:
When function() or macro() is called to define a new command, if a command
already exists with that name, the undocumented CMake behavior is to make the
old command available using the same name except with an underscore prepended.
This applies whether the old name is for a builtin command or a custom function
or macro. Developers who are aware of this behavior are sometimes tempted to
exploit it to try to create a wrapper around an existing command like so:
function(someFunc)
# Do something...
endfunction()
# Later in the project...
function(someFunc)
if(...)
# Override the behavior with something else...
else()
# WARNING: Intended to call the original command,
# but it is not safe
_someFunc()
endif()
endfunction()function(someFunc)
# Do something...
endfunction()
# Later in the project...
function(someFunc)
if(...)
# Override the behavior with something else...
else()
# WARNING: Intended to call the original command,
# but it is not safe
_someFunc()
endif()
endfunction()
如果该命令仅像这样被覆盖一次,那么它似乎可以工作,但如果再次覆盖它,则原始命令将无法再访问。前面添加一个下划线来“保存”前一个命令仅适用于当前名称,它不会递归地应用于所有先前的覆盖。这有可能导致无限递归,如以下人为示例所示:
If the command is only ever overridden like this once, it appears to work, but if it is overridden again, then the original command is no longer accessible. The prepending of one underscore to "save" the previous command only applies to the current name, it is not applied recursively to all previous overrides. This has the potential to lead to infinite recursion, as the following contrived example demonstrates:
function(printme)
message("Hello from first")
endfunction()
function(printme)
message("Hello from second")
_printme()
endfunction()
function(printme)
message("Hello from third")
_printme()
endfunction()
printme()function(printme)
message("Hello from first")
endfunction()
function(printme)
message("Hello from second")
_printme()
endfunction()
function(printme)
message("Hello from third")
_printme()
endfunction()
printme()
人们会天真地期望输出如下:
One would naively expect the output to be as follows:
来自第三位的你好 第二个你好 从一开始你好
Hello from third Hello from second Hello from first
但相反,第一个实现永远不会被调用,因为第二个实现最终会在无限循环中调用自身。当 CMake 处理上述内容时,会发生以下情况:
But instead, the first implementation is never called because the second one ends up calling itself in an infinite loop. When CMake processes the above, here’s what occurs:
printme已创建并可作为该名称的命令使用。以前不存在同名的命令,因此不需要采取进一步的操作。
printme is created and made available as a
command of that name. No command by that name previously existed, so no
further action is required.
printme遇到了第二次实现。CMake 通过该名称找到现有命令,因此它定义该名称_printme以指向旧命令并设置printme为指向新定义。
printme is encountered. CMake finds an
existing command by that name, so it defines the name _printme to point to
the old command and sets printme to point to the new definition.
printme遇到了第三次实现。同样,CMake 找到该名称的现有命令,因此它重新定义该名称_printme以指向旧命令(这是第二个实现)并设置
printme为指向新定义。
printme is encountered. Again, CMake finds an
existing command by that name, so it redefines the name _printme to
point to the old command (which is the second implementation) and sets
printme to point to the new definition.
当printme()被调用时,执行进入第三个实现,该实现调用_printme(). 这进入第二个实现,它也调用
_printme(),但_printme()再次指向第二个实现并导致无限递归结果。执行永远不会到达第一个实现。
When printme() is called, execution enters the third implementation, which
calls _printme(). This enters the second implementation which also calls
_printme(), but _printme() points back at the second implementation again
and infinite recursion results. Execution never reaches the first
implementation.
一般来说,只要不像上面讨论的那样尝试调用以前的实现,就可以覆盖函数或宏。项目应该简单地假设新的实现取代了旧的实现,旧的实现被认为不再可用。
In general, it is fine to override a function or macro as long as it does not try to call the previous implementation like in the above discussion. Projects should simply assume that the new implementation replaces the old one, with the old one considered to be no longer available.
CMake 3.17 添加了对许多变量的支持,以协助调试和实现功能。以下变量在函数执行期间可用:
CMake 3.17 added support for a number of variables to assist with debugging and implementing functions. The following variables will be available during execution of a function:
CMAKE_CURRENT_FUNCTION
CMAKE_CURRENT_FUNCTION
CMAKE_CURRENT_FUNCTION_LIST_FILE
CMAKE_CURRENT_FUNCTION_LIST_FILE
CMAKE_CURRENT_FUNCTION_LIST_DIR
CMAKE_CURRENT_FUNCTION_LIST_DIR
CMAKE_CURRENT_FUNCTION_LIST_LINE
CMAKE_CURRENT_FUNCTION_LIST_LINE
CMAKE_CURRENT_FUNCTION_LIST_DIR当函数需要引用作为函数内部实现细节的文件时,该变量特别有用。的值将包含调用CMAKE_CURRENT_LIST_DIR该函数的文件的目录,而保存定义该函数的目录。要了解如何使用它,请考虑以下示例。它演示了一种常见模式,其中函数使用命令
从与定义函数的文件相同的目录中复制文件(有关进一步讨论,请参阅第 19.2 节“复制文件”):CMAKE_CURRENT_FUNCTION_LIST_DIRconfigure_file()
The CMAKE_CURRENT_FUNCTION_LIST_DIR variable is particularly useful when a
function needs to refer to a file that is an internal implementation detail of
the function.
The value of CMAKE_CURRENT_LIST_DIR would contain the directory of the file
where the function is called, whereas CMAKE_CURRENT_FUNCTION_LIST_DIR holds
the directory where the function is defined.
To see how this can be used, consider the following example.
It demonstrates a common pattern where a function uses the configure_file()
command to copy a file from the same directory as the file defining the
function (see Section 19.2, “Copying Files” for further discussion):
function(writeSomeFile toWhere)
configure_file(
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/template.cpp.in
${toWhere}
@ONLY
)
endfunction()function(writeSomeFile toWhere)
configure_file(
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/template.cpp.in
${toWhere}
@ONLY
)
endfunction()
在 CMake 3.17 之前,上述内容通常会实现如下所示(CMake 自己的模块在 CMake 3.17 之前使用了此技术):
Before CMake 3.17, the above would typically be implemented something like the following instead (CMake’s own modules used this technique prior to CMake 3.17):
set(__writeSomeFile_DIR ${CMAKE_CURRENT_LIST_DIR})
function(writeSomeFile toWhere)
configure_file(
${__writeSomeFile_DIR}/template.cpp.in
${toWhere}
@ONLY
)
endfunction()set(__writeSomeFile_DIR ${CMAKE_CURRENT_LIST_DIR})
function(writeSomeFile toWhere)
configure_file(
${__writeSomeFile_DIR}/template.cpp.in
${toWhere}
@ONLY
)
endfunction()
第二个示例依赖于__writeSomeFile_DIR在调用函数时变量保持可见。通常,这应该是一个合理的假设,但由于函数具有全局作用域可见性,因此项目在技术上可以在一个位置定义函数并在不相关的变量作用域中调用它。虽然这在技术上是合法的,但不建议这样做。当在定义函数的文件中使用包含防护时,还必须特别小心使用此技术(请参见第 7.4 节“提前结束处理”)。
This second example relies on the __writeSomeFile_DIR variable remaining
visible at the point of the call to the function.
Normally, that should be a reasonable assumption, but because functions have
global scope visibility, projects can technically define a function in one
place and call it in an unrelated variable scope.
While that is technically legal, it is not a recommended practice.
Extra care must also be taken with this technique when include guards are used
in files that define functions (see Section 7.4, “Ending Processing Early”).
变量CMAKE_CURRENT_FUNCTION…仅针对函数进行更新,不会在宏内部进行修改。当执行宏代码时,这些变量将保存调用宏时它们所具有的任何值。
The CMAKE_CURRENT_FUNCTION… variables are only updated for functions, they
are not modified inside macros.
When executing code for a macro, these variables will hold whatever values they
had when the macro was called.
函数和宏提供了定义稍后执行的代码的强大方法。它们是重用通用逻辑来完成类似或重复任务的重要组成部分。然而,在某些情况下,项目可能希望定义 CMake 代码,使其以函数和宏本身无法捕获的方式执行。
Functions and macros provide powerful ways of defining code to be executed at some later time. They are an essential part of re-using common logic for similar or repetitive tasks. Nevertheless, there are situations where projects may want to define CMake code to be executed in a way that functions and macros alone cannot capture.
CMake 3.18 添加了cmake_language()可用于直接调用任意 CMake 代码的命令,而无需定义函数或宏。此功能并不是为了取代函数或宏而设计的,而是通过启用更简洁的代码以及以以前不可能的方式表达逻辑的能力来补充它们。CMake 3.18 提供的两个子命令是CALL和EVAL CODE:
CMake 3.18 added the cmake_language() command which can be used to invoke
arbitrary CMake code directly without having to define a function or macro.
This functionality isn’t designed to replace functions or macros, but rather to
complement them by enabling more concise code and the ability to express logic
in ways that were not previously possible.
The two sub-commands provided by CMake 3.18 are CALL and EVAL CODE:
cmake_language(CALL command [args...])
cmake_language(EVAL CODE code...)cmake_language(CALL command [args...])
cmake_language(EVAL CODE code...)
该CALL子命令调用单个 CMake 命令,并根据需要使用参数。它提供了参数化要调用的命令的能力,而无需对所有可用的选择进行硬编码。某些内置命令不能以这种方式调用,特别是那些开始或结束块的命令,如、
if()、endif()等。foreach()endforeach()
The CALL sub-command invokes a single CMake command, with arguments if
required.
It provides the ability to parameterize the command to be invoked without
having to hard-code all available choices.
Certain built-in commands cannot be invoked this way, specifically those
commands that start or end a block like if(), endif(), foreach(),
endforeach() and so on.
以下示例演示了如何围绕一组名称中包含版本号的函数定义通用包装器:
The following example demonstrates how a generic wrapper can be defined around a set of functions that include a version number in their name:
function(qt_generate_moc)
set(cmd qt${QT_DEFAULT_MAJOR_VERSION}_generate_moc)
cmake_language(CALL ${cmd} ${ARGV})
endfunction()function(qt_generate_moc)
set(cmd qt${QT_DEFAULT_MAJOR_VERSION}_generate_moc)
cmake_language(CALL ${cmd} ${ARGV})
endfunction()
上面的示例假设QT_DEFAULT_MAJOR_VERSION变量之前已设置。随着未来 Qt 主要版本的发布,只要仍然提供适当的版本控制命令,上述内容就会继续工作。另一种方法是if()为每个版本单独实施一组不断扩展的测试。
The above example assumes the QT_DEFAULT_MAJOR_VERSION variable has been set
previously.
As future Qt major versions are released, the above would continue to work as
long as the appropriate versioned command was still provided.
The alternative would be to implement an ever-expanding set of if() tests for
each version individually.
该CALL子命令的用途相当有限。子EVAL CODE命令功能更强大,因为它支持执行任何有效的 CMake 脚本。这样做的优点之一是它不会干扰在函数调用内更新的变量,例如ARGV等CMAKE_CURRENT_FUNCTION
。以下示例利用此行为来实现一种形式的函数调用跟踪:
The CALL sub-command is fairly limited in its usefulness.
The EVAL CODE sub-command is much more powerful, as it supports executing
any valid CMake script.
One advantage of this is that it does not interfere with variables that get
updated inside a function invocation, such as ARGV, CMAKE_CURRENT_FUNCTION
and so on.
The following example takes advantage of this behavior to implement a form of
call tracing for functions:
set(myProjTraceCall [=[
message("Called ${CMAKE_CURRENT_FUNCTION}")
set(__x 0)
while(__x LESS ${ARGC})
message(" ARGV${__x} = ${ARGV${__x}}")
math(EXPR __x "${__x} + 1")
endwhile()
unset(__x)
]=])
function(func)
cmake_language(EVAL CODE "${myProjTraceCall}")
# ...
endfunction()
func(one two three)set(myProjTraceCall [=[
message("Called ${CMAKE_CURRENT_FUNCTION}")
set(__x 0)
while(__x LESS ${ARGC})
message(" ARGV${__x} = ${ARGV${__x}}")
math(EXPR __x "${__x} + 1")
endwhile()
unset(__x)
]=])
function(func)
cmake_language(EVAL CODE "${myProjTraceCall}")
# ...
endfunction()
func(one two three)
称为函数 ARGV0 = 1 ARGV1 = 两个 ARGV2 = 3
Called func ARGV0 = one ARGV1 = two ARGV2 = three
请注意存储的代码如何myProjTraceCall使用各种ARG*
变量以及CMAKE_CURRENT_FUNCTION变量。括号语法[=[and]=]用于防止在myProjTraceCall设置时对这些变量进行求值。这些变量仅在调用时才会被求值cmake_language(),因此它们将反映封闭函数的详细信息。由于这种延迟评估,跟踪代码将无法在宏内按预期工作,因此只能在函数内部使用它。
Note how the code stored in myProjTraceCall makes use of the various ARG*
variables and also the CMAKE_CURRENT_FUNCTION variable.
Bracket syntax [=[ and ]=] is used to prevent the evaluation of these
variables when myProjTraceCall is set.
The variables will be evaluated only when cmake_language() is called, so they
will reflect the details of the enclosing function.
Because of this delayed evaluation, the tracing code won’t work as expected
inside a macro, so only use it from inside a function.
有关子命令的另一个特别有趣的示例,请参见第 8.8.2 节“转发命令参数” 。EVAL CODE
See Section 8.8.2, “Forwarding Command Arguments” for another particularly interesting
example of the EVAL CODE sub-command.
CMake 3.19 添加了DEFER一组子命令。这些允许命令排队等待稍后执行,并管理当前排队的命令集。创建延迟命令是通过以下形式完成的:
CMake 3.19 added the DEFER set of sub-commands.
These allow a command to be queued for execution at a later time and to manage
the set of currently queued commands.
Creating a deferred command is accomplished with the following form:
cmake_language(DEFER
[DIRECTORY dir]
[ID id | ID_VAR outVar]
CALL command [args...] ①
)cmake_language(DEFER
[DIRECTORY dir]
[ID id | ID_VAR outVar]
CALL command [args...] ①
)
command对和中的变量进行求值args并不遵循大多数其他 CMake 命令的通常行为。有关重要差异的讨论,请参见第 8.8.3 节“参数扩展的特殊情况” 。command and args doesn’t follow the
usual behavior of most other CMake commands.
See Section 8.8.3, “Special Cases For Argument Expansion” for a discussion of the
important differences.及其command参数将在当前目录范围末尾排队等待执行。可以提供该DIRECTORY选项来指定不同的目录范围。在这种情况下,dirCMake 必须已经知道该目录,并且它必须尚未完成处理。实际上,这意味着它必须是当前目录或父目录范围之一。
The command and its arguments will be queued for execution at the end of the
current directory scope.
The DIRECTORY option can be given to specify a different directory scope
instead.
In that case, the dir directory must already be known to CMake and it must
not already have finished being processed.
In practice, this means it must be either the current directory or one of the
parent directory scopes.
cmake_language(DEFER
CALL message "End of current scope processing"
)
cmake_language(DEFER
DIRECTORY ${CMAKE_SOURCE_DIR}
CALL message "End of top level processing"
)cmake_language(DEFER
CALL message "End of current scope processing"
)
cmake_language(DEFER
DIRECTORY ${CMAKE_SOURCE_DIR}
CALL message "End of top level processing"
)
每个排队的命令都有一个与其关联的标识符。多个命令可以与相同的标识符相关联,以允许它们作为一个组进行操作(参见下文)。通常,该项目会让 CMake 在对新的延迟命令进行排队时自动分配新的标识符。该ID_VAR选项可用于捕获分配的标识符,然后可以在以后的调用中重新使用该标识符,并可以选择ID向同一标识符添加更多命令。
Each queued command has an identifier associated with it.
Multiple commands can be associated with the same identifier to allow them to
be manipulated as a group (see further below).
Normally, the project would let CMake automatically assign a new identifier
when queueing a new deferred command.
The ID_VAR option can be used to capture the assigned identifier, which can
then be re-used in later calls with the ID option to add more commands to
the same identifier.
cmake_language(DEFER
ID_VAR deferredId
CALL message "First deferred command"
)
cmake_language(DEFER
ID ${deferredId}
CALL message "Second deferred command"
)cmake_language(DEFER
ID_VAR deferredId
CALL message "First deferred command"
)
cmake_language(DEFER
ID ${deferredId}
CALL message "Second deferred command"
)
其他DEFER子命令可以根据标识符查询和取消延迟命令:
Other DEFER sub-commands can query and cancel deferred commands based on
identifiers:
cmake_language(DEFER [DIRECTORY dir] GET_CALL_IDS outVar)
cmake_language(DEFER [DIRECTORY dir] GET_CALL id outVar)
cmake_language(DEFER [DIRECTORY dir] CANCEL_CALL ids...)cmake_language(DEFER [DIRECTORY dir] GET_CALL_IDS outVar)
cmake_language(DEFER [DIRECTORY dir] GET_CALL id outVar)
cmake_language(DEFER [DIRECTORY dir] CANCEL_CALL ids...)
该GET_CALL_IDS表单返回当前在指定目录范围内排队的所有命令的标识符列表,如果未DIRECTORY给出选项,则返回当前目录范围。该GET_CALL表单返回第一个命令及其与指定的相关的参数id。无法检索给定标识符的第二个或后续命令,也不可能检索与标识符相关联的命令的数量。该CANCEL_CALL表单将丢弃与任何指定标识符关联的所有延迟命令。
The GET_CALL_IDS form returns a list of the identifiers for all commands
currently queued for the specified directory scope, or the current directory
scope if no DIRECTORY option is given.
The GET_CALL form returns the first command and its arguments associated
with the specified id.
It is not possible to retrieve the second or later commands for a given
identifier, nor a count of how many commands are associated with an
identifier.
The CANCEL_CALL form will discard all deferred commands associated with any
of the specified identifiers.
此时,人们自然会开始考虑DEFER使用该功能的不同方式。在此之前,请考虑以下观察结果:
At this point, it would be natural to start thinking of different ways one
might put the DEFER functionality to use.
Before doing so, consider the following observations:
鉴于上述情况,如果有选择,优先选择其他技术或重构而不是推迟命令。例如,函数可能会包装创建目标的命令,然后在返回之前调用使用该目标属性的其他命令(属性将在下一章中介绍)。通过将所有这些封装在一个函数中,调用者没有机会在使用目标属性之前修改它们。不要推迟使用目标的命令以便调用者可以修改目标属性,而是考虑分解函数以使其不承担太多责任。要求传入目标而不是创建目标将是此特定示例的一种替代解决方案。
Given the above, where there is a choice, prefer other techniques or refactoring over deferring commands. As an example, a function might wrap a command that creates a target, then call other commands that use properties of that target before returning (properties are covered in the next chapter). By encapsulating all of this in a single function, the caller has no opportunity to modify target properties before they are used. Rather than deferring the commands that use the target so that the caller can modify the target properties, consider breaking up the function so that it doesn’t have so many responsibilities. Requiring the target to be passed in instead of creating it would be one alternative solution for this particular example.
CMake 对命令参数的实现包含一些微妙的行为。大多数情况下,这些行为不会导致问题,但偶尔会引起混乱或产生意外结果。为了理解这些行为存在的原因以及如何安全地处理它们,了解 CMake 构造命令并将参数传递给命令的方式会有所帮助。
CMake’s implementation of command arguments contains a few subtle behaviors. For the most part, these behaviors don’t lead to problems, but occasionally they can cause confusion or give rise to unexpected results. In order to appreciate why these behaviors exist and how to handle them safely, it helps to understand the way in which CMake constructs and passes arguments to commands.
考虑以下等效调用,其中someCommand可以是任何有效命令:
Consider the following equivalent calls where someCommand could be any valid
command:
someCommand(a b c)
someCommand(a b c)someCommand(a b c)
someCommand(a b c)
参数由空格分隔,连续的空格被视为单个参数分隔符。分号也充当参数分隔符,因此以下内容也相当于上面的内容:
Arguments are separated by spaces and consecutive spaces are treated as a single argument separator. Semi-colons act as argument separators too, so the following are also equivalent to the above:
someCommand(a b;c)
someCommand(a;;;;b;c)someCommand(a b;c)
someCommand(a;;;;b;c)
如果参数需要包含嵌入的空格或分号,则必须使用引号:
Where an argument needs to contain embedded spaces or semicolons, quoting must be used:
someCommand(a "b b" c)
someCommand(a "b;b" c)
someCommand(a;"b;b";c)someCommand(a "b b" c)
someCommand(a "b;b" c)
someCommand(a;"b;b";c)
上述所有三个调用都会导致三个参数传递给命令。第一个调用传递b b第二个参数,另外两个调用传递
b;b第二个参数。
All three of the above calls result in three arguments being passed to the
command.
The first call passes b b for the second argument, the other two calls pass
b;b for the second argument.
空格和分号的不同之处在于,当涉及变量求值且参数未加引号时,它们的处理方式:
Where spaces and semicolons differ is how they are handled when variable evaluation is involved and arguments are not quoted:
set(containsSpace "b b")
set(containsSemiColon "b;b")
someCommand(a ${containsSpace} c)
someCommand(a ${containsSemiColon} c)set(containsSpace "b b")
set(containsSemiColon "b;b")
someCommand(a ${containsSpace} c)
someCommand(a ${containsSemiColon} c)
第一次调用someCommand()导致传递三个参数,而第二次调用导致传递四个参数。in 中嵌入的空格containsSpace不充当参数分隔符,但 in 中嵌入的分号containsSemiColon可以充当参数分隔符。在执行任何变量评估之前,空格仅充当参数分隔符。这两种不同行为之间的相互作用可能会导致一些令人惊讶的结果:
The first call to someCommand() results in three arguments being passed,
whereas the second call results in four arguments.
The embedded space in containsSpace does not act as an argument separator,
but the embedded semicolon in containsSemiColon does.
Spaces only act as argument separators before any variable evaluation is
performed.
The interaction between these two different behaviors can lead to some
surprising results:
set(empty "")
set(space " ")
set(semicolon ";")
set(semiSpace "; ")
set(spaceSemi " ;")
set(spaceSemiSpace " ; ")
set(spaceSemiSemi " ;;")
set(semiSemiSpace ";; ")
set(spaceSemiSemiSpace " ;; ")
someCommand(${empty}) # 0 args
someCommand(${space}) # 1 arg
someCommand(${semicolon}) # 0 args
someCommand(${semiSpace}) # 1 arg
someCommand(${spaceSemi}) # 1 arg
someCommand(${spaceSemiSpace}) # 2 args
someCommand(${spaceSemiSemi}) # 1 arg
someCommand(${semiSemiSpace}) # 1 arg
someCommand(${spaceSemiSemiSpace}) # 2 argsset(empty "")
set(space " ")
set(semicolon ";")
set(semiSpace "; ")
set(spaceSemi " ;")
set(spaceSemiSpace " ; ")
set(spaceSemiSemi " ;;")
set(semiSemiSpace ";; ")
set(spaceSemiSemiSpace " ;; ")
someCommand(${empty}) # 0 args
someCommand(${space}) # 1 arg
someCommand(${semicolon}) # 0 args
someCommand(${semiSpace}) # 1 arg
someCommand(${spaceSemi}) # 1 arg
someCommand(${spaceSemiSpace}) # 2 args
someCommand(${spaceSemiSemi}) # 1 arg
someCommand(${semiSemiSpace}) # 1 arg
someCommand(${spaceSemiSemiSpace}) # 2 args
从上面的内容中应该注意到一些重要的观察结果:
Some important observations should be noted from the above:
如果参数包含任何变量求值,则可以通过引用参数来避免大部分混乱。这消除了对嵌入空格或分号的任何特殊解释。虽然通常不会有害,但这并不总是可取的。正如下一小节将强调的,在某些情况下, 要求参数不被引用,因为它们依赖于上述行为。
Much of the confusion can be avoided by quoting arguments if they contain any variable evaluations. This eliminates any special interpretation of embedded spaces or semicolons. While not generally harmful, this isn’t always desirable. As the next subsection will highlight, there are some situations which require arguments to be unquoted precisely because they rely on the above behavior.
考虑前面第 8.3 节“关键字参数”cmake_parse_arguments()中讨论的命令
。该命令的原始形式通常如下使用:
Consider the cmake_parse_arguments() command discussed earlier in
Section 8.3, “Keyword Arguments”.
The original form of this command is typically used like so:
function(func)
set(noValues ENABLE_A ENABLE_B)
set(singleValues FORMAT ARCH)
set(multiValues SOURCES IMAGES)
cmake_parse_arguments(
ARG
"${noValues}" "${singleValues}" "${multiValues}"
${ARGV}
)
endfunction()function(func)
set(noValues ENABLE_A ENABLE_B)
set(singleValues FORMAT ARCH)
set(multiValues SOURCES IMAGES)
cmake_parse_arguments(
ARG
"${noValues}" "${singleValues}" "${multiValues}"
${ARGV}
)
endfunction()
noValues请注意对,singleValues和
的评估的引用multiValues。计算时,每个变量都会生成一个包含分号的字符串。例如,${singleValues}将评估为FORMAT;ARCH。引号对于防止这些分号充当参数分隔符是必要的。最终结果是,cmake_parse_arguments()将看到ARG第一个参数、ENABLE_A;ENABLE_B第二个参数、FORMAT;ARCH第三个参数和
SOURCES;IMAGES第四个参数。
Note the quoting around the evaluation of noValues, singleValues and
multiValues.
When evaluated, each of these variables yields a string that contains a
semicolon.
For example, ${singleValues} will evaluate to FORMAT;ARCH.
The quotes are necessary to prevent those semicolons from acting as argument
separators.
The end result is that cmake_parse_arguments() will see ARG for its first
argument, ENABLE_A;ENABLE_B as the second, FORMAT;ARCH as the third and
SOURCES;IMAGES as the fourth.
通话结束时提供的内容${ARGV}没有引号。这是专门为了利用嵌入的分号将充当参数分隔符这一事实。该cmake_parse_arguments()命令将其接收到的第五个及后续参数解释为要解析的参数。通过使用不带引号的${ARGV},cmake_parse_arguments()可以看到与传递给 的相同的参数集func()。
The ${ARGV} provided at the end of the call does not have quotes.
This is specifically to take advantage of the fact that embedded semicolons
will act as argument separators.
The cmake_parse_arguments() command interprets the fifth and subsequent
arguments it receives as the arguments to parse.
By using an unquoted ${ARGV}, cmake_parse_arguments() sees the same set of
arguments as was passed in to func().
上述问题在于,${ARGV}在两种特定情况下, using 无法保留原始参数。考虑以下调用:
The problem with the above is that using ${ARGV} fails to preserve the
original arguments in two specific cases.
Consider the following calls:
func(a "" c)
func("a;b;c" "1;2;3")func(a "" c)
func("a;b;c" "1;2;3")
对于第一次调用,内部func()的评估${ARGV}将为a;;c。然而,如观察 3 中所述,两个分号将被合并,并且
cmake_parse_arguments()将仅将a和c视为要解析的参数。空洞的论点被默默地丢弃。${ARGV}对于第二次调用, will的评估为a;b;c;1;2;3。最初对func()had的调用a;b;c作为第一个参数和1;2;3第二个参数,但这通过${ARGV}求值而变得扁平化,并且
cmake_parse_arguments()命令看到六个单独的参数而不是两个列表。这两个问题都可以通过使用另一种形式的命令
cmake_parse_arguments()来避免${ARGV}直接求值来解决:
For the first call, inside func() the evaluation of ${ARGV} will be a;;c.
As noted in OBSERVATION 3, however, the two semicolons will be merged and
cmake_parse_arguments() will see only a and c as the arguments to be
parsed.
Empty arguments are silently dropped.
For the second call, the evaluation of ${ARGV} will be a;b;c;1;2;3.
The original call to func() had a;b;c as the first argument and 1;2;3 as
the second, but this gets flattened by the ${ARGV} evaluation and the
cmake_parse_arguments() command instead sees six individual arguments rather
than two lists.
Both of these problems can be solved by using the other form of the
cmake_parse_arguments() command to avoid evaluating ${ARGV} directly:
cmake_parse_arguments(
PARSE_ARGV 0 ARG
"${noValues}" "${singleValues}" "${multiValues}"
)cmake_parse_arguments(
PARSE_ARGV 0 ARG
"${noValues}" "${singleValues}" "${multiValues}"
)
在实践中,该cmake_parse_arguments()命令通常用于删除空参数或展平列表没有实际影响的情况。在这些情况下,cmake_parse_arguments()可以安全地调用任一形式的命令。如果参数需要完全按照传入的方式保留,
PARSE_ARGV则应始终使用该形式。
In practice, the cmake_parse_arguments() command is often used in situations
where dropping empty arguments or flattening lists has no real impact.
In these cases, either form of the cmake_parse_arguments() command can safely
be called.
Where the arguments need to be preserved exactly as passed in though, the
PARSE_ARGV form should always be used.
一个相对常见的需求是围绕现有命令创建某种包装器。项目可能希望支持一些额外的选项或删除现有的选项,或者可能希望在调用之前或之后执行某些处理。保留论点并在不改变其结构或丢失信息的情况下转发它们可能非常困难。
A relatively common need is to create some sort of wrapper around an existing command. The project may wish to support some extra options or remove existing ones, or it may want to perform certain processing before or after the call. Preserving arguments and forwarding them on without changing their structure or losing information can be surprisingly difficult.
考虑以下示例及其输出,它继承了上一小节中的要点之一:
Consider the following example and its output, which picks up on one of the points in the previous subsection:
function(printArgs)
message("ARGC = ${ARGC}\n"
"ARGN = ${ARGN}"
)
endfunction()
printArgs("a;b;c" "d;e;f")function(printArgs)
message("ARGC = ${ARGC}\n"
"ARGN = ${ARGN}"
)
endfunction()
printArgs("a;b;c" "d;e;f")
辅助GC = 2 ARGN = a;b;c;d;e;f
ARGC = 2 ARGN = a;b;c;d;e;f
的参数被printArgs()引用,因此该函数只看到两个参数。在形成 for 的值${ARGN}时,这两个列表用分号连接,结果是包含六个项目的单个列表。由于列表扁平化,参数的原始形式丢失了。当包装器命令尝试将参数转发到被包装的命令时,请考虑这样做的后果:
The arguments to printArgs() are quoted, so the function does see only two
arguments.
In forming the value for ${ARGN} though, these two lists are joined with a
semicolon and the result is a single list of six items.
The original form of the arguments is lost as a result of this list flattening.
Consider the consequences of this for a wrapper command when it attempts to
forward arguments to the command being wrapped:
function(inner)
message("inner:\n"
"ARGC = ${ARGC}\n"
"ARGN = ${ARGN}"
)
endfunction()
function(outer)
message("outer:\n"
"ARGC = ${ARGC}\n"
"ARGN = ${ARGN}"
)
inner(${ARGN}) # Naive forwarding, not robust
endfunction()
outer("a;b;c" "d;e;f")function(inner)
message("inner:\n"
"ARGC = ${ARGC}\n"
"ARGN = ${ARGN}"
)
endfunction()
function(outer)
message("outer:\n"
"ARGC = ${ARGC}\n"
"ARGN = ${ARGN}"
)
inner(${ARGN}) # Naive forwarding, not robust
endfunction()
outer("a;b;c" "d;e;f")
外: 辅助GC = 2 ARGN = a;b;c;d;e;f 内: ARGC = 6 ARGN = a;b;c;d;e;f
outer: ARGC = 2 ARGN = a;b;c;d;e;f inner: ARGC = 6 ARGN = a;b;c;d;e;f
该outer()函数希望包装该inner()函数并准确转发其参数,但如上面的输出所示,所看到的参数数量inner()不同。${ARGN}作为传递参数的一种方式,对 的求值会inner()
触发前面描述的列表展平行为。论证的原始结构丢失了。PARSE_ARGV该命令的形式可cmake_parse_arguments()用于避免列表扁平化:
The outer() function wants to wrap the inner() function and forward its
arguments exactly, but as the above output shows, the number of arguments
seen by inner() is different.
The evaluation of ${ARGN} as a way to pass arguments through to inner()
triggers the list flattening behavior described previously.
The original structure of the arguments is lost.
The PARSE_ARGV form of the cmake_parse_arguments() command can be used to
avoid this list flattening:
function(outer)
cmake_parse_arguments(PARSE_ARGV 0 FWD "" "" "")
inner(${FWD_UNPARSED_ARGUMENTS})
endfunction()function(outer)
cmake_parse_arguments(PARSE_ARGV 0 FWD "" "" "")
inner(${FWD_UNPARSED_ARGUMENTS})
endfunction()
由于没有要解析的关键字,给出的所有参数都outer()将放置在FWD_UNPARSED_ARGUMENTS. 正如第 8.3 节“关键字参数”中所述,当填充PARSE_ARGV形式
时,它会转义原始参数中任何嵌入的分号。因此,当该变量传递给 时,转义会保留原始参数的结构,并且会看到与 相同的参数。cmake_parse_arguments()FWD_UNPARSED_ARGUMENTSinner()inner()outer()
With no keywords to parse, all arguments given to outer() will be placed
in FWD_UNPARSED_ARGUMENTS.
As noted back in Section 8.3, “Keyword Arguments”, when the PARSE_ARGV form of
cmake_parse_arguments() populates FWD_UNPARSED_ARGUMENTS, it escapes
any embedded semicolons from the original arguments.
Therefore, when that variable is passed to inner(), the escaping preserves
the structure of the original arguments and inner() will see the same
arguments as outer().
不幸的是,上述技术仍然有一个弱点。作为观察 2 和 3 的结果,它不保留任何空参数。为了避免删除空参数,每个参数都需要单独列出并用引号引起来。CMake 3.18 或更高版本提供的命令cmake_language(EVAL CODE)提供了所需的功能:
Unfortunately, the above technique still has a weakness.
As a consequence of OBSERVATIONS 2 and 3, it does not preserve any empty
arguments.
To avoid dropping empty arguments, each argument needs to be listed
individually and be quoted.
The cmake_language(EVAL CODE) command available with CMake 3.18 or later
provides the functionality needed:
function(outer)
cmake_parse_arguments(PARSE_ARGV 0 FWD "" "" "")
set(quotedArgs "")
foreach(arg IN LISTS FWD_UNPARSED_ARGUMENTS)
string(APPEND quotedArgs " [===[${arg}]===]")
endforeach()
cmake_language(EVAL CODE "inner(${quotedArgs})")
endfunction()function(outer)
cmake_parse_arguments(PARSE_ARGV 0 FWD "" "" "")
set(quotedArgs "")
foreach(arg IN LISTS FWD_UNPARSED_ARGUMENTS)
string(APPEND quotedArgs " [===[${arg}]===]")
endforeach()
cmake_language(EVAL CODE "inner(${quotedArgs})")
endfunction()
请注意使用括号形式进行引用。这确保了任何带有嵌入引号的参数也将得到稳健的处理。
Note the use of the bracket form for quoting. This ensures that any arguments with embedded quotes will be handled robustly too.
上述实现提供了强大的参数转发,但它需要的最低 CMake 版本为 3.18 或更高版本。对于早期版本,该cmake_language()命令不可用。可以通过将要执行的命令写出到文件并要求 CMake 通过调用来处理该文件来实现等效功能
include(),但这是非常低效的,不建议作为通用解决方案。
The above implementation provides robust argument forwarding, but it requires
a minimum CMake version of 3.18 or higher.
For earlier versions, the cmake_language() command is not available.
An equivalent capability can be implemented by writing out the command to be
executed to a file and asking CMake to process that file via a call to
include(), but this is very inefficient and not recommended as a general
solution.
虽然上述技术通常运行良好,但某些内置命令以特殊方式处理其参数,导致它们偏离预期行为。这些异常分为两大类:cmake_language()和评估布尔表达式。
While the above techniques work well in general, some built-in commands handle
their arguments in special ways, causing them to diverge from the expected
behavior.
These exceptions fall into two main categories: cmake_language() and
evaluating boolean expressions.
cmake_language()cmake_language()
cmake_language(CALL)提供执行命令的另一种方法。要调用的命令可以由变量提供,而不必进行硬编码。为了忠实地重现正常的参数扩展和给转发到命令的参数的引用处理行为,需要在调用中将这些参数与命令本身分开
cmake_language(CALL)。以下示例演示了该限制:
cmake_language(CALL) provides an alternative way of executing a command.
The command to call can be provided by a variable instead of having to be
hard-coded.
In order to faithfully reproduce the normal argument expansion and quote
handling behavior for the arguments given to the forwarded-to command, those
arguments are required to be separated from the command itself in the call to
cmake_language(CALL).
The following example demonstrates the limitation:
# ERROR: command must be its own argument
set(cmdWithArgs message STATUS "Hello world")
cmake_language(CALL ${cmdWithArgs})
# OK: Command can be a variable expansion, but it must
# evaluate to a single value. Arguments can also be a
# variable expansion and they can evaluate to a list.
set(cmd message)
set(args STATUS "Hello world")
cmake_language(CALL ${cmd} ${args})# ERROR: command must be its own argument
set(cmdWithArgs message STATUS "Hello world")
cmake_language(CALL ${cmdWithArgs})
# OK: Command can be a variable expansion, but it must
# evaluate to a single value. Arguments can also be a
# variable expansion and they can evaluate to a list.
set(cmd message)
set(args STATUS "Hello world")
cmake_language(CALL ${cmd} ${args})
该cmake_language(DEFER CALL)命令具有类似的限制,但还有进一步的区别。对于要执行的命令,会立即执行变量求值,但对于命令参数,则会延迟求值。以下示例突出显示了此行为:
The cmake_language(DEFER CALL) command has similar restrictions, but it has
further differences.
Variable evaluations are performed immediately for the command to be executed,
but evaluations are deferred for the command arguments.
The following example highlights this behavior:
set(cmd message)
set(args "before deferral")
cmake_language(DEFER CALL ${cmd} ${args})
set(cmd somethingElse) # Doesn't affect the command
set(args "after deferral") # But this does!set(cmd message)
set(args "before deferral")
cmake_language(DEFER CALL ${cmd} ${args})
set(cmd somethingElse) # Doesn't affect the command
set(args "after deferral") # But this does!
延期后
after deferral
的评估${cmd}会立即发生,但${args}直到调用延迟命令时才会评估。在目录范围的末尾,args将具有值after deferral。
The evaluation of ${cmd} happens immediately, but ${args} is not evaluated
until the deferred command is called.
At the end of the directory scope, args will have the value after deferral.
如果需要立即执行命令参数中变量的计算,则必须将延迟包装在对
cmake_language(EVAL CODE). 此场景的一个示例是在函数或宏内部创建延迟,并且延迟的命令参数需要合并作为参数传递给延迟函数或宏的信息:
If evaluation of variables in command arguments needs to be performed
immediately, one has to wrap the deferral within a call to
cmake_language(EVAL CODE).
An example of this scenario is where the deferral is created inside a function
or macro and the deferred command arguments need to incorporate information
passed as arguments to the deferring function or macro:
function(endOfScopeMessage msg)
cmake_language(EVAL CODE "
cmake_language(DEFER CALL message [[${msg}]])
")
endfunction()function(endOfScopeMessage msg)
cmake_language(EVAL CODE "
cmake_language(DEFER CALL message [[${msg}]])
")
endfunction()
请注意如何在求值周围使用括号语法引用${msg},以确保正确处理求值变量中的空格。
Note how quoting with bracket syntax is used around the ${msg} evaluation to
ensure spaces in the evaluated variable are handled correctly.
将参数视为布尔表达式的命令也有一些与引用和参数扩展相关的特殊规则。该if()命令最好地说明了这一点,但规则也适用于
while(). 考虑以下示例,它显示了如何将未加引号的参数视为变量名或字符串值的微妙行为(
有关此行为的更深入讨论,请参阅第 6.1.1 节“基本表达式” ):
Commands that treat their arguments as a boolean expression also have some
special rules associated with quoting and argument expansion.
The if() command best demonstrates this, but the rules also apply to
while().
Consider the following example, which shows the subtle behavior of how unquoted
arguments can be treated as either variable names or string values (see
Section 6.1.1, “Basic Expressions” for a deeper discussion of this behavior):
cmake_minimum_required(VERSION 3.1)
set(someVar xxxx)
set(xxxx "some other value")
# Here, xxxx is unquoted, so it is treated as the name of
# a variable and its value is used. Result: prints NO
if(someVar STREQUAL xxxx)
message(YES)
else()
message(NO)
endif()
# Now use xxxx in quotes. This prevents it from being
# treated as a variable name and the value is used
# directly. Result: prints YES
if(someVar STREQUAL "xxxx")
message(YES)
else()
message(NO)
endif()cmake_minimum_required(VERSION 3.1)
set(someVar xxxx)
set(xxxx "some other value")
# Here, xxxx is unquoted, so it is treated as the name of
# a variable and its value is used. Result: prints NO
if(someVar STREQUAL xxxx)
message(YES)
else()
message(NO)
endif()
# Now use xxxx in quotes. This prevents it from being
# treated as a variable name and the value is used
# directly. Result: prints YES
if(someVar STREQUAL "xxxx")
message(YES)
else()
message(NO)
endif()
尝试使用变量来为命令提供参数来重现上述内容
if(),可以尝试如下操作:
Attempting to reproduce the above using variables to provide arguments to the
if() commands, one might try something like the following:
set(noQuotes someVar STREQUAL xxxx)
set(withQuotes someVar STREQUAL [["xxxx"]])
if(${noQuotes})
message(YES)
else()
message(NO)
endif()
if(${withQuotes}) # Doesn't work as expected
message(YES)
else()
message(NO)
endif()set(noQuotes someVar STREQUAL xxxx)
set(withQuotes someVar STREQUAL [["xxxx"]])
if(${noQuotes})
message(YES)
else()
message(NO)
endif()
if(${withQuotes}) # Doesn't work as expected
message(YES)
else()
message(NO)
endif()
的值withQuotes使用括号语法使引号成为存储值的一部分。我们的想法是尝试将if()命令视为xxxx带引号的参数,但它没有达到预期的效果。该命令在扩展之前if()检查引号,因此在这种情况下,引号将被视为参数值的一部分。当通过像上面这样的扩展变量求值提供参数时,无法将参数视为引用的参数。if()
The value of withQuotes uses bracket syntax to make the quotes part of the
value stored.
The idea is to try to get the if() command to treat xxxx as a quoted
argument, but it does not have the desired effect.
The if() command checks for quotes before expansion, so in this case, the
quotes get treated as part of the argument value.
There is no way to get an argument to be treated as quoted when providing the
arguments to if() through an expanded variable evaluation like in the above.
需要注意的另一种特殊情况是方括号影响列表中分号的解释方式,如前面第 5.8.1 节“不平衡方括号的问题”中所述。在计算变量时,不平衡方括号之间的分号不会被解释为列表分隔符。但这并没有扩展到 CMake 组装命令参数的方式,如以下示例所示:
One other special case to be aware of is the way square brackets affect how semicolons are interpreted for lists, as discussed previously in Section 5.8.1, “Problems With Unbalanced Square Brackets”. Semicolons between unbalanced square brackets are not interpreted as list separators when evaluating a variable. This does not extend to the way CMake assembles command arguments though, as demonstrated by the following example:
function(func)
message("Number of arguments: ${ARGC}")
math(EXPR lastIndex "${ARGC} - 1")
foreach(n RANGE 0 ${lastIndex})
message("ARGV${n} = ${ARGV${n}}")
endforeach()
foreach(arg IN LISTS ARGV)
message("${arg}")
endforeach()
endfunction()
func("a[a" "b]b" "c[c]c" "d[d" "eee")function(func)
message("Number of arguments: ${ARGC}")
math(EXPR lastIndex "${ARGC} - 1")
foreach(n RANGE 0 ${lastIndex})
message("ARGV${n} = ${ARGV${n}}")
endforeach()
foreach(arg IN LISTS ARGV)
message("${arg}")
endforeach()
endfunction()
func("a[a" "b]b" "c[c]c" "d[d" "eee")
这将产生以下输出:
This would produce the following output:
参数数量:5 ARGV0 = a[a ARGV1 = b]b ARGV2 = c[c]c ARGV3 = d[d ARGV4 = eee a[a;b]b c[c]c d[d;eee
Number of arguments: 5 ARGV0 = a[a ARGV1 = b]b ARGV2 = c[c]c ARGV3 = d[d ARGV4 = eee a[a;b]b c[c]c d[d;eee
该func()命令确实看到了五个原始参数,如第一个循环所示foreach()。ARGV第二个命令对变量的评估foreach()是嵌入的不平衡方括号干扰变量如何解释为列表的地方。
The func() command does see the five original arguments, as demonstrated by
the first foreach() loop.
The evaluation of the ARGV variable by the second foreach() command is
where the embedded unbalanced square brackets interfere with how the variable
is interpreted as a list.
在实践中,合理存在不平衡方括号的情况相对较少。有时,可能会遇到平衡方括号,但正如c[c]c
上面示例中的参数所示,它们不会干扰列表解释。
In practice, it is relatively uncommon to find situations where unbalanced
square brackets may reasonably be present.
Occasionally, balanced square brackets may be encountered, but as the c[c]c
argument in the above example shows, they do not interfere with list
interpretation.
函数和宏是在整个项目中重复使用同一段 CMake 代码的好方法。一般来说,更喜欢使用函数而不是宏,因为在函数内使用新的变量作用域可以更好地隔离该函数对调用作用域的影响。宏通常只应在宏体的内容确实需要在调用者的范围内执行的情况下使用。这些情况一般应该比较少见。为了避免意外行为,还应避免return()从宏内部调用。
Functions and macros are a great way to re-use the same piece of CMake code
throughout a project. In general, prefer to use functions rather than macros,
since the use of a new variable scope within the function better isolates that
function’s effects on the calling scope. Macros should generally only be used
where the contents of the macro body really do need to be executed within the
scope of the caller. These situations should generally be relatively rare. To
avoid unexpected behavior, also avoid calling return() from inside a macro.
更喜欢将函数或宏所需的所有值作为命令参数传递,而不是依赖于调用范围中设置的变量。这往往会使实现对未来的变化更加稳健,并且更清晰、更容易维护。
Prefer to pass all values a function or macro needs as command arguments rather than relying on variables being set in the calling scope. This tends to make the implementation more robust to future changes and it is much clearer and easier to maintain.
对于除非常简单的函数或宏之外的所有函数或宏,强烈建议使用cmake_parse_arguments(). 这会带来更好的可用性并提高调用代码的稳健性(例如,参数混淆的可能性很小)。它还允许该函数在将来更容易扩展,因为不依赖于参数排序,也不依赖于始终提供所有参数,即使不相关。
For all but very trivial functions or macros, it is highly recommended to use
the keyword-based argument handling provided by cmake_parse_arguments(). This
leads to better usability and improved robustness of calling code (e.g.
little chance of getting arguments mixed up). It also allows the function to be
more easily extended in the future because there is no reliance on argument
ordering or for all arguments to always be provided, even if not relevant.
解析或转发命令参数时,请注意不要删除空参数和列表扁平化。在项目的最低 CMake 版本允许的情况下,更喜欢使用
内部函数PARSE_ARGV的形式。cmake_parse_arguments()转发参数时,cmake_language(EVAL CODE)如果需要保留空参数和列表,请单独引用每个参数。
Beware of dropping empty arguments and list flattening when parsing or
forwarding command arguments.
Where the project’s minimum CMake version allows, prefer to use the
PARSE_ARGV form of cmake_parse_arguments() inside functions.
When forwarding arguments, use cmake_language(EVAL CODE) to quote each
argument individually if preserving empty arguments and lists is required.
cmake_language(DEFER)如果有其他选择,最好避免通过延迟命令。延迟的命令会带来脆弱性,阻碍调试项目的能力,并且可能是 CMake 函数和宏应该重构的标志。
Prefer to avoid deferring commands via cmake_language(DEFER) if there are
other alternatives.
Deferred commands introduce fragility, hinder the ability to debug a project
and can be a sign that CMake functions and macros should be refactored.
XXX.cmake常见的做法不是在整个源代码树中分布函数和宏,而是指定一个可以收集各种文件的特定目录(通常位于项目顶层下方) 。该目录就像一个随时可用的功能目录,可以从项目中的任何位置方便地访问。每个文件都可以提供函数、宏、变量和其他适当的功能。使用
.cmake文件名后缀允许命令将文件作为模块查找,第 11 章“模块”include()中详细介绍了该主题。它还倾向于允许 IDE 工具识别文件类型并应用 CMake 语法突出显示。
Rather than distributing functions and macros throughout the source tree, a
common practice is to nominate a particular directory (usually just below the
top level of the project) where various XXX.cmake files can be collected.
That directory acts like a catalog of ready-to-use functionality, able to be
conveniently accessed from anywhere in the project. Each of the files can
provide functions, macros, variables and other features as appropriate. Using a
.cmake file name suffix allows the include() command to find the files as
modules, a topic covered in detail in Chapter 11, Modules. It also tends to allow IDE
tools to recognize the file type and apply CMake syntax highlighting.
不要定义或调用名称以单下划线开头的函数或宏。特别是,不要依赖未记录的行为,即当函数或宏重新定义现有命令时,命令的旧实现可以通过这样的名称来使用。一旦命令被覆盖多次,其原始实现就不再可访问。这种未记录的行为甚至可能会在 CMake 的未来版本中被删除,因此不应使用它。类似地,不要覆盖任何内置 CMake 命令。将这些视为禁区,以便项目始终能够假定内置命令的行为符合官方文档,并且原始命令不会变得无法访问。
Do not define or call a function or macro with a name that starts with a single underscore. In particular, do not rely on the undocumented behavior whereby the old implementation of a command is made available by such a name when a function or macro redefines an existing command. Once a command has been overridden more than once, its original implementation is no longer accessible. This undocumented behavior may even be removed in a future version of CMake, so it should not be used. Along similar lines, do not override any builtin CMake command. Consider those to be off-limits so that projects will always be able to assume the builtin commands behave as per the official documentation and there will be no opportunity for the original command to become inaccessible.
如果项目的最低 CMake 版本设置为 3.17 或更高版本,则首选用于
CMAKE_CURRENT_FUNCTION_LIST_DIR引用预期存在于与定义函数的文件相关的位置的任何文件或目录。
Where the project’s minimum CMake version is set to 3.17 or later, prefer to use
CMAKE_CURRENT_FUNCTION_LIST_DIR to refer to any file or directory expected to
exist at a location relative to the file in which a function is defined.
属性影响构建过程的几乎所有方面,从源文件如何编译为目标文件,一直到打包安装程序中构建的二进制文件的安装位置。它们总是附加到特定的实体,无论是目录、目标、源文件、测试用例、缓存变量甚至整个构建过程本身。属性不像变量那样保存独立值,而是提供特定于它所附加到的实体的信息。
Properties affect just about all aspects of the build process, from how a source file is compiled into an object file, right through to the install location of built binaries in a packaged installer. They are always attached to a specific entity, whether that be a directory, target, source file, test case, cache variable or even the overall build process itself. Rather than holding a standalone value like a variable does, a property provides information specific to the entity it is attached to.
对于 CMake 新手来说,属性有时会与变量混淆。尽管两者在功能和特性方面最初看起来相似,但属性的用途却截然不同。变量不附加到任何特定实体,项目定义和使用自己的变量是很常见的。将此与 CMake 通常定义和记录且始终适用于特定实体的属性进行比较。造成两者之间混淆的一个可能原因是属性的默认值有时是由变量提供的。CMake 用于相关属性和变量的名称通常遵循相同的模式,变量名称是CMAKE_前面加上的属性名称。
For those new to CMake, properties are sometimes confused with variables.
Though both may initially seem similar in terms of function and features,
properties serve a very different purpose. A variable is not attached to any
particular entity and it is very common for projects to define and use their
own variables. Compare this with properties which are typically well defined
and documented by CMake and which always apply to a specific entity. A likely
contributor to the confusion between the two is that a property’s default value
is sometimes provided by a variable. The names CMake uses for related
properties and variables usually follow the same pattern, with the variable
name being the property name with CMAKE_ prepended.
CMake 提供了许多用于操作属性的命令。其中最通用的是set_property()和get_property(),允许设置和获取任何类型实体的任何属性。这些命令要求将实体的类型以及一些特定于实体的信息指定为命令参数。
CMake provides a number of commands for manipulating properties. The most
generic of these, set_property() and get_property(), allow setting and
getting any property on any type of entity. These commands require the type of
entity to be specified as command arguments along with some entity-specific
information.
set_property(entitySpecific
[APPEND | APPEND_STRING]
PROPERTY propertyName values...
)set_property(entitySpecific
[APPEND | APPEND_STRING]
PROPERTY propertyName values...
)
entitySpecific定义正在设置其属性的实体。它必须是
以下之一:
entitySpecific defines the entity whose property is being set. It must be
one of the following:
GLOBAL
DIRECTORY [dir]
TARGET targets...
SOURCE sources... # Additional options with CMake 3.18
INSTALL files...
TEST tests...
CACHE vars...GLOBAL
DIRECTORY [dir]
TARGET targets...
SOURCE sources... # Additional options with CMake 3.18
INSTALL files...
TEST tests...
CACHE vars...
上述每个的第一个字定义了正在设置其属性的实体的类型。
GLOBAL表示构建本身,因此不需要特定的实体名称。对于DIRECTORY,如果没有dir指定,则使用当前源目录。对于所有其他类型的实体,可以列出任意数量的该类型的项目。SOURCE使用 CMake 3.18 或更高版本时,实体类型还支持一些附加选项,这些选项将在第9.5 节“源属性”中讨论。
The first word of each of the above defines the type of entity whose property
is being set.
GLOBAL means the build itself, so there is no specific entity name required.
For DIRECTORY, if no dir is named, the current source directory is used.
For all the other types of entities, any number of items of that type can be
listed.
The SOURCE entity type also supports some additional options when using
CMake 3.18 or later, which are discussed in Section 9.5, “Source Properties”.
该PROPERTY关键字将所有剩余参数标记为定义属性名称及其值。通常propertyName会与 CMake 文档中定义的属性之一匹配,其中一些属性将在后面的章节中讨论。值的含义是特定于属性的。
The PROPERTY keyword marks all remaining arguments as defining the property
name and its value(s). The propertyName would normally match one of the
properties defined in the CMake documentation, a number of which are discussed
in later chapters. The meaning of the value(s) are property specific.
除了 CMake 定义的属性之外,项目还可以设置自己的自定义属性。这些属性的含义以及它们如何影响构建取决于项目。如果选择这样做,项目最好在属性名称上使用特定于项目的前缀,以避免与 CMake 或其他第三方包定义的属性发生潜在的名称冲突。
In addition to the properties defined by CMake, a project may also set its own custom properties. It is up to the project what such properties mean and how they affect the build. If choosing to do this, it would be wise for projects to use a project-specific prefix on the property name to avoid potential name clashes with properties defined by CMake or other third party packages.
set_property(TARGET MyApp1 MyApp2
PROPERTY MYPROJ_CUSTOM_PROP val1 val2 val3
)set_property(TARGET MyApp1 MyApp2
PROPERTY MYPROJ_CUSTOM_PROP val1 val2 val3
)
上面的示例定义了一个自定义MYPROJ_CUSTOM_PROP属性,该属性将列表val1;val2;val3作为其值。它还演示了如何同时为多个目标设置属性。
The above example defines a custom MYPROJ_CUSTOM_PROP property which will
have the list val1;val2;val3 as its value.
It also demonstrates how to set a property for multiple targets at once.
和关键字可用于控制命名属性(如果已具有值)的更新方式APPEND。APPEND_STRING如果未指定任何关键字,则给定的值将替换任何先前的值。关键字APPEND更改了将值附加到现有值的行为,形成一个列表,而APPEND_STRING关键字采用现有值并通过将两个值连接为字符串而不是列表来附加新值(另请参阅继承属性的特别说明如下)。下表展示了这些差异。
The APPEND and APPEND_STRING keywords can be used to control how the named
property is updated if it already has a value. With neither keyword specified,
the value(s) given replace any previous value. The APPEND keyword changes the
behavior to append the value(s) to the existing one, forming a list, whereas
the APPEND_STRING keyword takes the existing value and appends the new
value(s) by concatenating the two as strings rather than as a list (see also
the special note for inherited properties further below). The following table
demonstrates the differences.
| 以前的值) | 新值 | 无关键字 | 附加 | APPEND_STRING |
|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
该get_property()命令遵循类似的形式:
The get_property() command follows a similar form:
get_property(resultVar entitySpecific
PROPERTY propertyName
[DEFINED | SET | BRIEF_DOCS | FULL_DOCS]
)get_property(resultVar entitySpecific
PROPERTY propertyName
[DEFINED | SET | BRIEF_DOCS | FULL_DOCS]
)
该PROPERTY关键字始终是必需的,并且后面始终跟有要检索的属性的名称。检索结果存储在名称由 指定的变量中resultVar。该entitySpecific部分与 for 的部分类似,set_property()并且必须是以下之一:
The PROPERTY keyword is always required and is always followed by the name of
the property to retrieve. The result of the retrieval is stored in a variable
whose name is given by resultVar. The entitySpecific part is similar to
that for set_property() and must be one of the following:
GLOBAL
DIRECTORY [dir]
TARGET target
SOURCE source # Additional options with CMake 3.18
INSTALL file
TEST test
CACHE var
VARIABLEGLOBAL
DIRECTORY [dir]
TARGET target
SOURCE source # Additional options with CMake 3.18
INSTALL file
TEST test
CACHE var
VARIABLE
和以前一样,GLOBAL指的是整个构建,因此不需要命名特定的实体。DIRECTORY可以在指定或不指定特定目录的情况下使用,如果未提供目录,则假定当前源目录。对于大多数其他范围,必须命名该范围内的特定实体,并且将检索附加到该实体的请求属性。同样,在使用 CMake 3.18 或更高版本时,实体类型支持其他选项,这些选项将在第 9.5 节“源属性”SOURCE中介绍。
As before, GLOBAL refers to the build as a whole and therefore requires no
specific entity to be named. DIRECTORY can be used with or without specifying
a particular directory, with the current source directory being assumed if no
directory is provided. For most of the other scopes, the particular entity
within that scope must be named and the requested property attached to that
entity will be retrieved.
Again, the SOURCE entity type supports additional options when using
CMake 3.18 or later and these are covered in Section 9.5, “Source Properties”.
类型VARIABLE有点不同,变量名被指定为 thepropertyName而不是附加到VARIABLE关键字上。这看起来有点不直观,但请考虑一下变量与关键字一起命名为实体的情况VARIABLE,就像其他实体类型关键字一样。在这种情况下,无需为属性名称指定任何内容。将其视为VARIABLE指定当前
范围可能会有所帮助,那么感兴趣的属性就是由 命名的变量propertyName。当以这种方式理解时,VARIABLE与其他实体类型的处理方式是一致的。
The VARIABLE type is a bit different, with the variable name being specified
as the propertyName rather than being attached to the VARIABLE keyword. This
can seem somewhat unintuitive, but consider the situation if the variable was
named as the entity along with the VARIABLE keyword, just like for the other
entity type keywords. In that situation, there would be nothing to specify for
the property name. It may help to think of VARIABLE as specifying the current
scope, then the property of interest is the variable named by propertyName.
When understood this way, VARIABLE is consistent with how the other entity
types are handled.
如果未给出任何可选关键字,则检索指定属性的值。这是该命令的典型用法get_property()。VARIABLE请注意,在实践中,范围 with的使用get_property()相对不常见。通过语法可以直接获取变量值${},比使用 更清晰、更简单get_property()。
If none of the optional keywords are given, the value of the named property is
retrieved. This is the typical usage of the get_property() command. Note that
in practice, the use of VARIABLE scope with get_property() is relatively
uncommon. Variable values can be obtained directly with the ${} syntax, which
is both clearer and simpler than using get_property().
可选关键字可用于检索有关属性的详细信息,而不仅仅是其值:
The optional keywords can be used to retrieve details about the property other than just its value:
DEFINED
DEFINED
VARIABLE
,仅当已使用命令显式定义命名变量时,结果才会为 true define_property()(见下文)。
VARIABLE
scope queries, the result will only be true if the named variable has been
explicitly defined with the define_property() command (see below).
SET
SET
DEFINED在于它查询指定属性是否实际上已设置为某个值(该值本身不相关),而DEFINED更多的是描述该属性的含义。SET通常是项目需要的,而不是DEFINED
大多数场景中需要的。另请注意,属性可以返回 trueDEFINED和 false SET,反之亦然。
DEFINED in that it
queries whether the named property has actually been set to some value (the
value itself is irrelevant), whereas DEFINED is more about describing what
the property means. SET is usually what projects need rather than DEFINED
in most scenarios. Note also that a property can return true for DEFINED and
false for SET, or vice versa.
BRIEF_DOCS
BRIEF_DOCS
NOTFOUND。
NOTFOUND.
FULL_DOCS
FULL_DOCS
NOTFOUND。
NOTFOUND.
在可选关键字中,除非SET项目已明确调用define_property()来填充特定实体所请求的信息,否则几乎没有什么价值:
Of the optional keywords, all but SET have little value unless the project
has explicitly called define_property() to populate the requested
information for the particular entity:
define_property(entityType
PROPERTY propertyName [INHERITED]
BRIEF_DOCS briefDoc [moreBriefDocs...]
FULL_DOCS fullDoc [moreFullDocs...]
)define_property(entityType
PROPERTY propertyName [INHERITED]
BRIEF_DOCS briefDoc [moreBriefDocs...]
FULL_DOCS fullDoc [moreFullDocs...]
)
该define_property()命令很少使用。它不设置属性的值,仅设置其文档以及是否从其他地方继承其值(如果尚未设置)。必须是、、、、
或entityType之一,并且指定所定义的属性。没有指定实体,尽管与命令一样,在变量名称的情况下指定为。简短的文档通常应保持在相对较短的一行,而完整的文档可以更长,并且如果需要的话可以跨多行。GLOBALDIRECTORYTARGETSOURCETESTVARIABLECACHED_VARIABLEpropertyNameget_property()VARIABLEpropertyName
The define_property() command is rarely used.
It does not set the property’s value, only its documentation and whether it
inherits its value from elsewhere if it has not been set.
The entityType must be one of GLOBAL, DIRECTORY, TARGET, SOURCE,
TEST, VARIABLE or CACHED_VARIABLE and the propertyName specifies the
property being defined.
No entity is specified, although like for the get_property() command, in the
case of VARIABLE the variable name is specified as propertyName.
The brief docs should generally be kept to one relatively short line, while the
full docs can be longer and span across multiple lines if required.
如果INHERITED在定义属性时使用该选项,
get_property()并且未在命名范围中设置该属性,则该命令将链接到父范围。例如,如果DIRECTORY请求属性但未为指定目录设置属性,则将在目录范围层次结构中递归查询父目录范围,直到找到该属性或到达源树的顶层。如果在顶级目录中仍未找到,则将GLOBAL搜索范围。同样,如果请求TARGET,SOURCE或TEST属性,但未为指定实体设置,DIRECTORY则将搜索范围(包括在目录层次结构中递归向上,并GLOBAL在必要时最终到达范围)。VARIABLE没有为或
提供此类链接功能CACHE,因为它们已按设计链接到父变量范围。
If the INHERITED option is used when defining a property, the
get_property() command will chain up to the parent scope if that property is
not set in the named scope. For example, if a DIRECTORY property is requested
but is not set for the directory specified, the parent directory scope is
queried recursively up the directory scope hierarchy until the
property is found or the top level of the source tree is reached. If still not
found at the top level directory, then the GLOBAL scope will be searched.
Similarly, if a TARGET, SOURCE or TEST property is requested but is not
set for the specified entity, the DIRECTORY scope will be searched (including
recursively up the directory hierarchy and ultimately to the GLOBAL scope if
necessary). No such chaining functionality is provided for VARIABLE or
CACHE, since these already chain to the parent variable scope by design.
属性的继承行为INHERITED仅适用于
特定属性类型的get_property()命令及其类似get_…函数(在下面的部分中介绍)。当使用 或 选项调用时set_property()
,APPEND仅APPEND_STRING考虑属性的直接值(即,在计算要附加的值时不会发生继承)。
The inheriting behavior of INHERITED properties only applies to the
get_property() command and its analogous get_… functions for specific
property types (covered in the sections below). When calling set_property()
with APPEND or APPEND_STRING options, only the immediate value of the
property is considered (i.e. no inheriting occurs when working out the value
to append to).
CMake 每种类型都有大量预定义属性。开发人员应查阅 CMake 参考文档,了解可用属性及其预期用途。在后面的章节中,我们将讨论其中的许多属性,并探讨它们与其他 CMake 命令、变量和功能的关系。
CMake has a large number of pre-defined properties of each type. Developers should consult the CMake reference documentation for the available properties and their intended purpose. In later chapters, many of these properties are discussed and their relationship to other CMake commands, variables and features are explored.
全局属性与整体构建相关。它们通常用于修改构建工具的启动方式或工具行为的其他方面,用于定义项目文件的结构方式以及提供某种程度的构建级别信息。
Global properties relate to the overall build as a whole. They are typically used for things like modifying how build tools are launched or other aspects of tool behavior, for defining aspects of how project files are structured and for providing some degree of build-level information.
除了通用set_property()和get_property()命令之外,CMake 还提供get_cmake_property()查询全局实体的功能。get_property()尽管它可以简单地用于检索任何全局属性的值,但它不仅仅是 的简写。
In addition to the generic set_property() and get_property() commands,
CMake also provides get_cmake_property() for querying global entities. It
is more than just shorthand for get_property(), although it can be used
simply to retrieve the value of any global property.
get_cmake_property(resultVar property)get_cmake_property(resultVar property)
就像 for 一样get_property(),resultVar是一个变量的名称,当命令返回时,所请求的属性的值将存储在该变量中。参数property可以是任何全局属性的名称或以下伪属性之一:
Just like for get_property(), resultVar is the name of a variable in which
the value of the requested property will be stored when the command returns.
The property argument can be the name of any global property or one of the
following pseudo properties:
VARIABLES
VARIABLES
CACHE_VARIABLES
CACHE_VARIABLES
COMMANDS
COMMANDS
MACROS
MACROS
COMMANDS,但请注意,名称的大写/小写可能与COMMANDS伪属性报告的内容不同。
COMMANDS pseudo property would return, but note that the upper/lower
case of the names can be different to what the COMMANDS pseudo property
reports.
COMPONENTS
COMPONENTS
install()中介绍。
install() commands,
which is covered in Chapter 26, Installing.
从技术上讲,这些只读伪属性不是全局属性(get_property()例如,无法使用 检索它们),但它们在概念上非常相似。它们只能通过 检索get_cmake_property()。
These read-only pseudo properties are technically not global properties (they
cannot be retrieved using get_property(), for example), but they are
notionally very similar. They can only be retrieved via get_cmake_property().
目录还支持它们自己的属性集。从逻辑上讲,目录属性位于适用于任何地方的全局属性和仅影响单个目标的目标属性之间。因此,目录属性主要侧重于设置目标属性的默认值以及覆盖全局属性或当前目录的默认值。一些只读目录属性还提供一定程度的内省,保存有关构建如何到达目录、此时定义了哪些内容等的信息。
Directories also support their own set of properties. Logically, directory properties sit somewhere between global properties which apply everywhere and target properties which only affect individual targets. As such, directory properties mostly focus on setting defaults for target properties and overriding global properties or defaults for the current directory. A few read-only directory properties also provide a degree of introspection, holding information about how the build reached the directory, what things have been defined at that point, etc.
为了方便起见,CMake 提供了用于设置和获取目录属性的专用命令,这些命令比通用命令更简洁:
For convenience, CMake provides dedicated commands for setting and getting directory properties which are a little more concise than their generic counterparts:
set_directory_properties(PROPERTIES
prop1 val1
[prop2 val2] ...
)
get_directory_property(resultVar
[DIRECTORY dir] property
)
get_directory_property(resultVar
[DIRECTORY dir] DEFINITION varName
)set_directory_properties(PROPERTIES
prop1 val1
[prop2 val2] ...
)
get_directory_property(resultVar
[DIRECTORY dir] property
)
get_directory_property(resultVar
[DIRECTORY dir] DEFINITION varName
)
虽然更加简洁,但这个特定于目录的 setter 命令缺少任何APPEND或APPEND_STRING选项。这意味着它只能用于设置或替换属性,不能用于直接添加到现有属性。与更通用的命令相比,此命令的进一步限制
set_property()是它始终适用于当前目录。项目可以选择在方便的地方使用这种更具体的形式,并在其他地方使用通用形式,或者为了保持一致性,可以在任何地方使用更通用的形式。这两种方法都不是更正确的,这更多的是一个偏好问题。
While being a little more concise, this directory-specific setter command lacks
any APPEND or APPEND_STRING option. This means it can only be used to set
or replace a property, it cannot be used to add to an existing property
directly. A further restriction of this command compared to the more generic
set_property() is that it always applies to the current directory. Projects
may choose to use this more specific form where it is convenient and use the
generic form elsewhere, or for consistency the more generic form may be used
everywhere. Neither approach is more correct, it’s more a matter of preference.
特定于目录的 getter 命令有两种形式。DIRECTORY第一种形式用于从特定目录或当前目录(如果未使用参数)获取属性值。第二种形式检索变量的值,这可能看起来不太有用,但它提供了一种从当前目录范围之外的不同目录范围获取变量值的方法(当DIRECTORY
使用参数时)。实际上,应该很少需要第二种形式,并且除了调试构建或类似的临时任务之外,应该避免使用它。
The directory-specific getter command has two forms.
The first form is used to get the value of a property from a particular
directory or from the current directory if the DIRECTORY argument is not
used. The second form retrieves the value of a variable, which may not seem
all that useful, but it provides a means of obtaining a variable’s value from a
different directory scope other than the current one (when the DIRECTORY
argument is used). In practice, this second form should rarely be needed and
its use should be avoided for scenarios other than debugging the build or
similar temporary tasks.
对于任一形式的命令get_directory_property(),如果DIRECTORY
使用参数,则指定的目录必须已由 CMake 处理。CMake 不可能知道它尚未遇到的目录范围的属性。
For either form of the get_directory_property() command, if the DIRECTORY
argument is used, the named directory must have already been processed by
CMake. It is not possible for CMake to know the properties of a directory scope
it has not yet encountered.
CMake 中几乎没有什么东西可以对如何将目标构建为目标属性产生如此强烈和直接的影响。它们控制并提供从用于编译源文件的标志到构建的二进制文件和中间文件的类型和位置的所有信息。一些目标属性影响目标在开发人员的 IDE 项目中的呈现方式,而其他属性则影响编译/链接时使用的工具。简而言之,目标属性是收集和应用有关如何实际将源文件转换为二进制文件的大部分细节的地方。
Few things in CMake have such a strong and direct influence on how targets are built as target properties. They control and provide information about everything from the flags used to compile source files through to the type and location of the built binaries and intermediate files. Some target properties affect how targets are presented in the developer’s IDE project, while others affect the tools used when compiling/linking. In short, target properties are where most of the details about how to actually turn source files into binaries are collected and applied.
CMake 中发展了许多用于操作目标属性的方法。除了通用命令set_property()和get_property()命令之外,CMake 还提供了一些特定于目标的等效项以方便使用:
A number of methods have evolved in CMake for manipulating target properties.
In addition to the generic set_property() and get_property() commands,
CMake also provides some target-specific equivalents for convenience:
set_target_properties(target1 [target2...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ...
)
get_target_property(resultVar target propertyName)set_target_properties(target1 [target2...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ...
)
get_target_property(resultVar target propertyName)
至于set_directory_properties()命令,set_target_properties()
缺乏完整的灵活性,set_property()但为常见情况提供了更简单的语法。该set_target_properties()命令不支持附加到现有属性值,如果需要为给定属性提供列表值,该set_target_properties()命令要求以字符串形式指定该值,例如"this;is;a;list"。
As for the set_directory_properties() command, set_target_properties()
lacks the full flexibility of set_property() but provides a simpler syntax
for common cases. The set_target_properties() command does not support
appending to existing property values and if a list value needs to be provided
for a given property, the set_target_properties() command requires that value
to be specified in string form, e.g. "this;is;a;list".
该get_target_property()命令是 的简化版本
get_property()。它纯粹专注于提供一种简单的方法来获取目标属性的值,基本上只是通用命令的简写版本。
The get_target_property() command is the simplified version of
get_property(). It focuses purely on providing a simple way to obtain the
value of a target property and is basically just a shorthand version of the
generic command.
除了通用和特定于目标的属性 getter 和 setter 之外,CMake 还有许多其他修改目标属性的命令。特别是,命令系列target_…()是 CMake 的关键部分,除了最琐碎的项目之外的所有项目通常都会使用它们。这些命令不仅定义特定目标的属性,还定义如何将该信息传播到链接到它的其他目标。
第 15 章,编译器和链接器要点介绍了这些命令以及它们如何与目标属性深入相关。
In addition to the generic and target-specific property getters and setters,
CMake also has a number of other commands which modify target properties. In
particular, the family of target_…() commands are a critical part of CMake
and all but the most trivial of projects would typically use them. These
commands define not only properties for a particular target, they also define
how that information might be propagated to other targets that link to it.
Chapter 15, Compiler And Linker Essentials covers those commands and how they relate
to target properties in depth.
CMake 还支持单个源文件的属性。这些可以在逐个文件的基础上对编译器标志进行细粒度操作,而不是针对所有目标源。它们还允许提供有关源文件的附加信息,以修改 CMake 或构建工具处理文件的方式。例如,它们可能指示文件是否作为构建的一部分生成、使用什么编译器、处理该文件的非编译器工具的选项等等。
CMake also supports properties on individual source files. These enable fine-grained manipulation of compiler flags on a file-by-file basis rather than for all of a target’s sources. They also allow additional information about the source file to be provided to modify how CMake or build tools treat the file. For example, they may indicate whether the file is generated as part of the build, what compiler to use with it, options for non-compiler tools working with the file and so on.
项目很少需要查询或修改源文件属性,但对于需要它的情况,CMake 提供了专用的 setter 和 getter 命令来使任务变得更容易。这些命令遵循与其他特定于属性的 setter 和 getter 命令类似的模式:
Projects should rarely need to query or modify source file properties, but for those situations that require it, CMake provides dedicated setter and getter commands to make the task easier. These follow a similar pattern to the other property-specific setter and getter commands:
set_source_files_properties(file1 [file2...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ...
)
get_source_file_property(resultVar sourceFile propertyName)set_source_files_properties(file1 [file2...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ...
)
get_source_file_property(resultVar sourceFile propertyName)
同样,没有APPEND为 setter 提供任何功能,而 getter 实际上只是通用命令的语法简写get_property(),并且不提供新功能。以下示例展示了如何在源文件上设置属性,在本例中是为了防止它与 Unity 构建中的其他源组合(在第30.1 节“Unity 构建”中讨论):
Again, no APPEND functionality is provided for the setter, while the getter
is really just syntax shorthand for the generic get_property() command and
offers no new functionality.
The following example shows how to set a property on a source file, in this
case to prevent it from being combined with other sources in a unity build
(discussed in Section 30.1, “Unity Builds”):
add_executable(MyApp small.cpp big.cpp tall.cpp thin.cpp)
set_source_files_properties(big.cpp PROPERTIES
SKIP_UNITY_BUILD_INCLUSION YES
)add_executable(MyApp small.cpp big.cpp tall.cpp thin.cpp)
set_source_files_properties(big.cpp PROPERTIES
SKIP_UNITY_BUILD_INCLUSION YES
)
对于 CMake 3.17 及更早版本,源属性仅对同一目录范围中定义的目标可见。如果源属性的设置发生在不同的目录范围中,则目标将不会看到该属性更改,因此该源文件的编译等不会受到影响。使用 CMake 3.18 或更高版本,可以使用其他选项来指定应在其中搜索或应用源文件属性的目录范围。下面显示了可用于使用 CMake 3.18 或更高版本设置源文件属性的完整选项集:
With CMake 3.17 and earlier, source properties are only visible to targets defined in the same directory scope. If the setting of a source property occurs in a different directory scope, the target will not see that property change and therefore the compilation, etc. of that source file will not be affected. With CMake 3.18 or later, additional options are available to specify the directory scope in which the source file properties should be searched or applied. The following shows the full set of options available for setting source file properties with CMake 3.18 or later:
set_property(SOURCE sources...
[DIRECTORY dirs...]
[TARGET_DIRECTORY targets...]
[APPEND | APPEND_STRING]
PROPERTY propertyName values...
)
set_source_files_properties(sources...
[DIRECTORY dirs...]
[TARGET_DIRECTORY targets...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ...
)set_property(SOURCE sources...
[DIRECTORY dirs...]
[TARGET_DIRECTORY targets...]
[APPEND | APPEND_STRING]
PROPERTY propertyName values...
)
set_source_files_properties(sources...
[DIRECTORY dirs...]
[TARGET_DIRECTORY targets...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ...
)
该DIRECTORY选项可用于指定应在其中设置源属性的一个或多个目录。在这些目录中创建的任何目标都将了解源属性。这些目录必须已经通过之前的调用添加到构建中add_subdirectory()。任何相对路径都将被视为相对于当前源目录。
The DIRECTORY option can be used to specify one or more directories in which
the source properties should be set.
Any targets created in those directories will be aware of the source
properties.
These directories must have already been added to the build by an earlier call
to add_subdirectory().
Any relative paths will be treated as relative to the current source directory.
该TARGET_DIRECTORY选项类似,只是后面跟着目标名称。对于列出的每个目标,创建该目标的目录(即其源目录)将被视为已使用该
DIRECTORY选项指定。请注意,这意味着该目录中定义的所有目标都将了解源属性,而不仅仅是指定的目标。
The TARGET_DIRECTORY option is similar, except it is followed by the names of
targets.
For each target listed, the directory in which that target was created (i.e.
its source directory) will be treated as though it had been specified with the
DIRECTORY option.
Note that this means all targets defined in that directory will be aware of
the source property, not just the target specified.
CMake 3.18 还添加了用于检索源文件属性的类似选项:
CMake 3.18 also added analogous options for retrieving source file properties:
get_property(resultVar SOURCE source
[DIRECTORY dir | TARGET_DIRECTORY target]
PROPERTY propertyName
[DEFINED | SET | BRIEF_DOCS | FULL_DOCS]
)
get_source_file_property(resultVar source
[DIRECTORY dir | TARGET_DIRECTORY target]
propertyName
)get_property(resultVar SOURCE source
[DIRECTORY dir | TARGET_DIRECTORY target]
PROPERTY propertyName
[DEFINED | SET | BRIEF_DOCS | FULL_DOCS]
)
get_source_file_property(resultVar source
[DIRECTORY dir | TARGET_DIRECTORY target]
propertyName
)
检索源文件属性时,最多可以列出一个目标或目录来标识从中检索属性的目录范围。DIRECTORY如果既没有给出也没有
给出,则假定当前源目录TARGET_DIRECTORY。
When retrieving source file properties, at most one target or directory can be
listed to identify the directory scope from which to retrieve the property.
The current source directory is assumed if neither DIRECTORY nor
TARGET_DIRECTORY is given.
无论是否DIRECTORY指定TARGET_DIRECTORY或未指定选项,请注意,一个源文件有可能被编译为多个目标。因此,在设置源属性的每个目录范围中,这些属性应对使用这些文件的所有目标都有意义。
Whether DIRECTORY or TARGET_DIRECTORY options are specified or not, note
that it is possible for a source file to be compiled into multiple targets.
Therefore, in each of the directory scopes where the source properties are set,
the properties should make sense for all targets using those files.
开发人员应该了解实现细节,这可能会在某些情况下对其使用产生强烈的阻碍。对于某些 CMake 生成器(尤其是 Unix Makefiles 生成器),源和源属性之间的依赖关系比人们预期的要强。如果使用源属性来修改特定源文件而不是整个目标的编译器标志,则更改源的编译器标志仍将导致重建所有目标的源,而不仅仅是受影响的源文件。这是在 Makefile 中处理依赖项详细信息的方式的限制,其中测试每个源的编译器标志是否已更改会带来极大的性能损失。相关的 Makefile 依赖项是在目标级别实现的,以避免该问题。
Developers should be aware of an implementation detail which may present a strong deterrent to their use in some situations. For some CMake generators (notably the Unix Makefiles generator), the dependencies between sources and source properties are stronger than one might expect. If source properties are used to modify the compiler flags for specific source files rather than for a whole target, changing the source’s compiler flags will still result in all of the target’s sources being rebuilt, not just the affected source file. This is a limitation of how the dependency details are handled in the Makefile, where testing whether each individual source’s compiler flags have changed brings with it a prohibitively big performance hit. The relevant Makefile dependencies were implemented at the target level instead to avoid that problem.
项目可能倾向于使用源属性的一种典型场景是将版本详细信息作为编译器定义传递给一个或两个源。正如第 20.2 节“源代码访问版本详细信息”中所讨论的,源属性有更好的替代方案,它们不会遭受上述构建性能问题。设置某些源属性还可以通过阻止这些源参与 Unity 构建来降低构建性能(请参阅第 30.1 节 “Unity 构建”)。
A typical scenario where projects may be tempted to use source properties is to pass version details to just one or two sources as compiler definitions. As discussed in Section 20.2, “Source Code Access To Version Details”, there are better alternatives to source properties which do not suffer from the sort of build performance problems mentioned above. Setting some source properties can also reduce build performance by preventing those sources from participating in unity builds (see Section 30.1, “Unity Builds”).
Xcode 生成器在对源属性的支持方面也存在限制,这使其无法处理特定于配置的属性值。有关此限制可能很重要的场景,请参见第 15.5 节“特定于语言的编译器标志” 。
The Xcode generator also has a limitation in its support for source properties which prevents it from handling configuration-specific property values. See Section 15.5, “Language-specific Compiler Flags” for a scenario of where this limitation can be important.
缓存变量上的属性与其他属性类型的用途略有不同。在大多数情况下,缓存变量属性更多地针对如何在 CMake GUI 和基于控制台的
ccmake工具中处理缓存变量,而不是以任何有形的方式影响构建。也没有提供额外的命令来操作它们,因此通用
set_property()和命令必须与
关键字get_property()一起使用CACHE
Properties on cache variables are a little different in purpose to other
property types. For the most part, cache variable properties are aimed more at
how the cache variables are handled in the CMake GUI and the console-based
ccmake tool rather than affecting the build in any tangible way. There are
also no extra commands provided for manipulating them, so the generic
set_property() and get_property() commands must be used with the CACHE
keyword
在第 5.3 节“缓存变量”中,讨论了缓存变量的许多方面,这些方面最终反映在缓存变量属性中。
In Section 5.3, “Cache Variables”, a number of aspects of cache variables were discussed which are ultimately reflected in the cache variable properties.
BOOL、FILEPATH、
PATH或STRING之一INTERNAL。get_property()可以使用属性 name 来获取此类型
TYPE。该类型会影响 CMake GUI 的方式以及ccmake在 UI 中呈现该缓存变量的方式,以及使用哪种小部件来编辑其值。任何具有类型的变量INTERNAL都不会显示。
BOOL, FILEPATH,
PATH, STRING or INTERNAL. This type can be obtained using
get_property() with the property name TYPE. The type affects how the CMake
GUI and ccmake present that cache variable in the UI and what kind of widget
is used for editing its value. Any variable with type INTERNAL will not be
shown at all.
mark_as_advanced()
,这实际上只是设置布尔ADVANCED缓存变量属性。CMake GUI 和该ccmake工具都提供了显示或隐藏高级缓存变量的选项,允许用户选择是只关注主要基本变量还是查看全套非内部变量。
mark_as_advanced()
command, which is really just setting the boolean ADVANCED cache variable
property. The CMake GUI and the ccmake tool both provide an option to show or
hide advanced cache variables, allowing the user to choose whether to focus on
just the main basic variables or to see the full set of non-internal variables.
set(),但也可以使用HELPSTRING
缓存变量属性进行修改或读取。此帮助字符串用作 CMake GUI 中的工具提示以及工具中的一行帮助提示ccmake。
set() command, but it can also be modified or read using the HELPSTRING
cache variable property. This help string is used as the tooltip in the CMake
GUI and as a one-line help tip in the ccmake tool.
STRING,则 CMake GUI 将查找名为 的缓存变量属性STRINGS。如果不为空,则预计它是变量的有效值列表,然后 CMake GUI 会将该变量呈现为这些值的组合框,而不是任意文本输入小部件。在 的情况下ccmake,在该缓存变量上按 Enter 键将循环显示提供的值。请注意,CMake 并不强制要求缓存变量必须是属性中的值之一STRINGS,这只是为 CMake GUI 和工具提供方便ccmake。cmake当 CMake 运行其配置步骤时,它仍然将缓存变量视为任意字符串,因此仍然可以在命令行或通过
set()项目中的命令为缓存变量赋予任何值。
STRING, then CMake GUI will look for a cache
variable property named STRINGS. If not empty, it is expected to be a list of
valid values for the variable and CMake GUI will then present that variable as
a combo box of those values rather than an arbitrary text entry widget. In the
case of ccmake, pressing enter on that cache variable will cycle through the
values provided. Note that CMake does not enforce that the cache variable must
be one of the values from the STRINGS property, it is only a convenience for
the CMake GUI and ccmake tools. When CMake runs its configure step, it still
treats the cache variable as an arbitrary string, so it is still possible to
give the cache variable any value either at the cmake command line or via
set() commands in the project.
CMake 还支持单个测试的属性,并提供属性 setter 和 getter 命令的常用测试特定版本:
CMake also supports properties on individual tests and it provides the usual test-specific versions of the property setter and getter commands:
set_tests_properties(test1 [test2...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ...
)
get_test_property(resultVar test propertyName)set_tests_properties(test1 [test2...]
PROPERTIES
propertyName1 value1
[propertyName2 value2] ...
)
get_test_property(resultVar test propertyName)
与它们的等效命令一样,这些只是通用命令的稍微简洁的版本,缺乏APPEND功能,但在某些情况下可能更方便。测试将在第 25 章“测试”中详细讨论
。
Like their equivalent counterparts, these are just slightly more concise
versions of the generic commands which lack APPEND functionality but may be
more convenient in some circumstances. Tests are discussed in detail in
Chapter 25, Testing.
CMake 支持的另一种属性类型是针对已安装的文件。这些属性特定于所使用的包装类型,并且大多数项目通常不需要。
The other type of property CMake supports is for installed files. These properties are specific to the type of packaging being used and are typically not needed by most projects.
属性是 CMake 的重要组成部分。一系列命令能够设置、修改或查询各种类型的属性,其中一些对项目之间的依赖关系有进一步的影响。
Properties are a crucial part of CMake. A range of commands have the ability to set, modify or query the various types of properties, some of which have further implications for dependencies between projects.
set_property()命令进行完全操作,从而使开发人员可以预测并APPEND在需要时提供灵活的功能。在某些情况下,特定于属性的设置器可能更方便,例如允许一次设置多个属性,但它们缺乏APPEND功能可能会导致某些项目仅使用set_property(). 尽管一个常见的错误是使用特定于属性的命令来替换属性值而不是附加到它,但两者都没有对错之分。
set_property() command, making it predictable for developers and
offering flexible APPEND functionality where needed. The property-specific
setters may be more convenient in some situations, such as allowing multiple
properties to be set at once, but their lack of APPEND functionality may
steer some projects towards just using set_property(). Neither is right or
wrong, although a common mistake is to use the property-specific commands to
replace a property value instead of appending to it.
target_…()命令,而不是直接操作关联的目标属性。这些命令不仅可以操作特定目标上的属性,还可以在目标之间建立依赖关系,以便 CMake 可以自动传播一些属性。第 15 章,编译器和链接器要点讨论了一系列主题,突出了对target_…()
命令的强烈偏好。
target_…() commands is strongly
recommended over manipulating the associated target properties directly. These
commands not only manipulate the properties on specific targets, they also set
up dependency relationships between targets so that CMake can propagate some
properties automatically. Chapter 15, Compiler And Linker Essentials discusses a range
of topics which highlight the strong preference for the target_…()
commands.
运行 CMake 时,开发人员倾向于将其视为一个步骤,其中涉及读取项目CMakeLists.txt文件并生成相关的一组特定于生成器的项目文件(例如 Visual Studio 解决方案和项目文件、Xcode 项目、Unix Makefile 或 Ninja 输入文件) )。然而,涉及两个截然不同的步骤。运行 CMake 时,输出日志的末尾通常如下所示:
When running CMake, developers tend to think of it as a single step which
involves reading the project’s CMakeLists.txt file and producing the relevant
set of generator-specific project files (e.g. Visual Studio solution and
project files, an Xcode project, Unix Makefiles or Ninja input files). There
are, however, two quite distinct steps involved. When running CMake, the end
of the output log typically looks something like this:
-- 配置完成 -- 生成完成 -- 构建文件已写入:/some/path/build
-- Configuring done -- Generating done -- Build files have been written to: /some/path/build
调用 CMake 时,它首先读入并处理CMakeLists.txt
源树顶部的文件,包括它引入的任何其他文件。在执行命令、函数等时,将创建项目的内部表示。这称为配置步骤。控制台日志的大部分输出都是在此阶段生成的,包括
message()命令中的任何内容。在配置步骤结束时,
-- Configuring done消息将打印到日志中。
When CMake is invoked, it first reads in and processes the CMakeLists.txt
file at the top of the source tree, including any other files it pulls in. An
internal representation of the project is created as the commands, functions,
etc. are executed. This is called the configure step. Most of the output to
the console log is produced during this stage, including any content from
message() commands. At the end of the configure step, the
-- Configuring done message is printed to the log.
一旦 CMake 完成CMakeLists.txt文件的读取和处理,它就会执行生成步骤。这是使用配置步骤中构建的内部表示创建构建工具的项目文件的地方。大多数情况下,开发人员倾向于忽略生成步骤,而只是将其视为配置的最终结果。控制台日志几乎总是-- Generating done在配置步骤完成后立即显示消息,因此这是可以理解的。但在某些情况下,理解两个不同阶段的分离尤为重要。
Once CMake has finished reading and processing the CMakeLists.txt file, it
then performs the generation step. This is where the build tool’s project
files are created using the internal representation built up in the configure
step. For the most part, developers tend to ignore the generation step and just
think of it as the end result of configuration. The console log almost always
shows the -- Generating done message immediately after the configure step
completes, so this is understandable. But there are situations where
understanding the separation into two distinct phases is particularly
important.
考虑为多配置 CMake 生成器(如 Xcode、Visual Studio 或 Ninja Multi-Config)处理的项目。CMakeLists.txt读取文件时,CMake 不知道将为哪种配置构建目标。它是一个多配置设置,因此有多个选择(例如调试、发布等)。开发人员在构建时(CMake 完成后很久)选择配置。如果文件想要执行诸如将文件复制到与给定目标的最终可执行文件相同的目录的操作,这似乎会出现问题
CMakeLists.txt,因为该目录的位置取决于正在构建的配置。需要一个占位符来告诉 CMake“对于正在构建的配置,使用最终可执行文件的目录”。
Consider a project processed for a multi configuration CMake generator like
Xcode, Visual Studio or Ninja Multi-Config.
When the CMakeLists.txt files are being read, CMake
doesn’t know which configuration a target will be built for. It is a
multi configuration setup, so there’s more than one choice (e.g. Debug,
Release, etc.). The developer selects the configuration at build time, well
after CMake has finished. This would seem to present a problem if the
CMakeLists.txt file wants to do something like copy a file to the same
directory as the final executable for a given target, since the location of
that directory depends on which configuration is being built.
A placeholder is needed to tell CMake "For whichever configuration is being
built, use the directory of the final executable".
这是生成器表达式提供的功能的一个主要示例。它们提供了一种对某些逻辑进行编码的方法,这些逻辑在配置时不进行评估,而是将评估延迟到写入项目文件的生成阶段。它们可用于执行条件逻辑、输出字符串,提供有关构建各个方面的信息,如目录、事物名称、平台详细信息等。它们甚至可以用于根据是否正在执行构建或安装来提供不同的内容。
This is a prime example of the functionality provided by generator expressions. They provide a way to encode some logic which is not evaluated at configure time, the evaluation is instead delayed until the generation phase when the project files are being written. They can be used to perform conditional logic, output strings providing information about various aspects of the build like directories, names of things, platform details and more. They can even be used to provide different content based on whether a build or an install is being performed.
生成器表达式不能到处使用,但很多地方都支持它们。在 CMake 参考文档中,如果特定命令或属性支持生成器表达式,文档将提及它。支持生成器表达式的属性集随着时间的推移而扩展,一些 CMake 版本也扩展了支持的表达式集。项目应确认对于他们所需的最低 CMake 版本,正在修改的属性确实支持所使用的生成器表达式。
Generator expressions cannot be used everywhere, but they are supported in many places. In the CMake reference documentation, if a particular command or property supports generator expressions, the documentation will mention it. The set of properties supporting generator expressions have expanded over time, with some CMake releases also expanding the set of supported expressions. Projects should confirm that for the minimum CMake version they require, the properties being modified do indeed support the generator expressions used.
生成器表达式是使用以下语法指定的,$<…>其中尖括号之间的内容可以采用几种不同的形式。正如很快就会清楚的那样,一个基本特征是有条件地包含内容。最基本的生成器表达式如下:
A generator expression is specified using the syntax $<…> where the content
between the angle brackets can take a few different forms. As will become clear
shortly, an essential feature is the conditional inclusion of content. The most
basic generator expressions for this are the following:
$<1:...>
$<0:...>$<1:...>
$<0:...>
对于$<1:…>,表达式的结果将是该…部分,而对于$<0:…>,该…部分被忽略并且表达式结果为空字符串。这些基本上是 true 和 false 条件表达式,但与变量不同,true 和 false 的概念只允许这两个特定值。条件表达式中除 0 或 1 之外的任何内容都会被 CMake 拒绝并出现致命错误。另一个生成器表达式可用于使布尔表达式求值更加灵活,并确保内容求值为 0 或 1:
For $<1:…>, the result of the expression will be the … part, whereas
for $<0:…>, the … part is ignored and the expression results in an
empty string.
These are basically the true and false conditional expressions, but unlike for
variables, the concept of true and false only allows for these two specific
values.
Anything other than 0 or 1 for a conditional expression is rejected by CMake
with a fatal error.
Another generator expression can be used to make boolean expression evaluation
more flexible and ensure content evaluates to 0 or 1:
$<BOOL:...>$<BOOL:...>
这以与该命令评估布尔常量…相同的方式评估内容,因此它理解所有常见的特殊字符串,如、等。一种非常常见的模式是使用它来包装预期保存布尔值的变量的计算,但该变量可能不限于 0 或 1(有关示例,请参阅下面的表格)。if()OFFNOFALSE
This evaluates the … content in the same way that the if() command
evaluates a boolean constant, so it understands all the usual special strings
like OFF, NO, FALSE and so on.
A very common pattern is to use it to wrap the evaluation of a variable that is
expected to hold a boolean value, but which might not be restricted to 0 or 1
(see the table a little further below for examples).
还支持逻辑运算:
Logical operations are also supported:
$<AND:expr[,expr...]>
$<OR:expr[,expr...]>
$<NOT:expr>$<AND:expr[,expr...]>
$<OR:expr[,expr...]>
$<NOT:expr>
每个表达式expr的计算结果预计为 1 或 0。ANDandOR
表达式可以采用任意数量的逗号分隔参数并提供相应的逻辑结果,而 whileNOT仅接受单个表达式并将产生其参数的否定。由于AND、OR和NOT要求它们的表达式的计算结果仅为 0 或 1,因此请考虑将这些表达式包装在 a 中,$<BOOL:…>以强制对被视为 true 或 false 表达式的逻辑更加宽容。
Each expr is expected to evaluate to either 1 or 0. The AND and OR
expressions can take any number of comma-separated arguments and provide the
corresponding logic result, while NOT accepts only a single expression and
will yield the negation of its argument. Because AND, OR and NOT require
that their expressions evaluate to only 0 or 1, consider wrapping those
expressions in a $<BOOL:…> to force more tolerant logic of what is
considered a true or false expression.
使用 CMake 3.8 及更高版本,还可以使用专用表达式非常方便地表达 if-then-else 逻辑$<IF:…>:
With CMake 3.8 and later, if-then-else logic can also be expressed very
conveniently using a dedicated $<IF:…> expression:
$<IF:expr,val1,val0>$<IF:expr,val1,val0>
像往常一样,expr必须计算为 1 或 0。结果是val1ifexpr
计算为 1 和val0ifexpr计算为 0。在 CMake 3.8 之前,必须以以下更详细的方式表达等效逻辑,需要给出两次表达式:
As usual, the expr must evaluate to 1 or 0. The result is val1 if expr
evaluates to 1 and val0 if expr evaluates to 0. Before CMake 3.8, equivalent
logic would have to be expressed in the following more verbose way that
requires the expression to be given twice:
$<expr:val1>$<$<NOT:expr>:val0>$<expr:val1>$<$<NOT:expr>:val0>
生成器表达式可以嵌套,从而允许构造任意复杂度的表达式。上面的示例显示了嵌套条件,但生成器表达式的任何部分都可以嵌套。以下示例演示了迄今为止讨论的功能:
Generator expressions can be nested, allowing expressions of arbitrary complexity to be constructed. The above example shows a nested condition, but any part of a generator expression can be nested. The following examples demonstrate the features discussed so far:
| 表达 | 结果 |
|---|---|
|
|
|
|
|
错误,不是 1 或 0 Error, not a 1 or 0 |
|
|
|
|
|
|
|
错误,NOT 需要 1 或 0 Error, NOT requires a 1 or 0 |
|
|
|
|
|
|
|
结果将是 Result will be |
就像命令一样if(),CMake 也支持测试生成器表达式中的字符串、数字和版本,尽管语法略有不同。如果满足各自的条件,则以下全部评估为 1,否则评估为 0。
Just like for the if() command, CMake also provides support for testing
strings, numbers and versions in generator expressions, although the syntax is
slightly different. The following all evaluate to 1 if the respective condition
is satisfied, or 0 otherwise.
$<STREQUAL:string1,string2>
$<EQUAL:number1,number2>
$<VERSION_EQUAL:version1,version2>
$<VERSION_GREATER:version1,version2>
$<VERSION_LESS:version1,version2>$<STREQUAL:string1,string2>
$<EQUAL:number1,number2>
$<VERSION_EQUAL:version1,version2>
$<VERSION_GREATER:version1,version2>
$<VERSION_LESS:version1,version2>
另一个非常有用的条件表达式是测试构建类型:
Another very useful conditional expression is testing the build type:
$<CONFIG:arg>$<CONFIG:arg>
arg如果对应于实际正在构建的构建类型,则计算结果为 1 ;对于所有其他构建类型,计算结果为 0。其常见用途是仅为调试构建提供编译器标志或为不同的构建类型选择不同的实现。例如:
This will evaluate to 1 if arg corresponds to the build type actually being
built and 0 for all other build types. Common uses of this would be to provide
compiler flags only for debug builds or to select different implementations for
different build types. For example:
add_executable(MyApp src1.cpp src2.cpp)
# Before CMake 3.8
target_link_libraries(MyApp PRIVATE
$<$<CONFIG:Debug>:CheckedAlgo>
$<$<NOT:$<CONFIG:Debug>>:FastAlgo>
)
# CMake 3.8 or later allows a more concise form
target_link_libraries(MyApp
PRIVATE $<IF:$<CONFIG:Debug>,CheckedAlgo,FastAlgo>
)add_executable(MyApp src1.cpp src2.cpp)
# Before CMake 3.8
target_link_libraries(MyApp PRIVATE
$<$<CONFIG:Debug>:CheckedAlgo>
$<$<NOT:$<CONFIG:Debug>>:FastAlgo>
)
# CMake 3.8 or later allows a more concise form
target_link_libraries(MyApp
PRIVATE $<IF:$<CONFIG:Debug>,CheckedAlgo,FastAlgo>
)
上面的代码会将可执行文件链接到CheckedAlgo用于构建的库Debug
以及FastAlgo用于所有其他构建类型的库。生成
$<CONFIG:…>器表达式是稳健地提供适用于所有 CMake 项目生成器的功能的唯一方法,包括 Xcode、Visual Studio 或 Ninja Multi-Config 等多配置生成器。第 14.2 节“常见错误”更详细地介绍了这个特定主题。
The above would link the executable to the CheckedAlgo library for Debug
builds and to the FastAlgo library for all other build types. The
$<CONFIG:…> generator expression is the only way to robustly provide such
functionality which works for all CMake project generators, including
multi configuration generators like Xcode, Visual Studio or Ninja Multi-Config.
This particular topic is covered in more detail in Section 14.2, “Common Errors”.
CMake 提供了更多基于平台和编译器详细信息、CMake 策略设置等的条件测试。开发人员应查阅 CMake 参考文档,了解支持的全套条件表达式。
CMake offers even more conditional tests based on things like platform and compiler details, CMake policy settings, etc. Developers should consult the CMake reference documentation for the full set of supported conditional expressions.
生成器表达式的另一个常见用途是提供有关目标的信息。目标的任何属性都可以通过以下两种形式之一获得:
Another common use of generator expressions is to provide information about targets. Any property of a target can be obtained with one of the following two forms:
$<TARGET_PROPERTY:target,property>
$<TARGET_PROPERTY:property>$<TARGET_PROPERTY:target,property>
$<TARGET_PROPERTY:property>
第一种形式提供来自指定目标的命名属性的值,而第二种形式将从使用生成器表达式的目标检索属性。
The first form provides the value of the named property from the specified target, while the second form will retrieve the property from the target on which the generator expression is being used.
虽然TARGET_PROPERTY它是一种非常灵活的表达类型,但它并不总是获取目标信息的最佳方式。例如,CMake 还提供其他表达式,提供有关目标构建的二进制文件的目录和文件名的详细信息。这些更直接的表达式负责提取某些属性的一部分或基于原始属性计算值。其中最通用的是一TARGET_FILE组生成器表达式:
While TARGET_PROPERTY is a very flexible expression type, it is not always
the best way to obtain information about a target. For example, CMake also
provides other expressions which give details about the directory and file name
of a target’s built binary. These more direct expressions take care of
extracting out parts of some properties or computing values based on raw
properties. The most general of these is the TARGET_FILE set of generator
expressions:
TARGET_FILE
TARGET_FILE
.exe,.dylib)。对于基于 Unix 的平台,共享库的文件名中通常包含版本详细信息,这些也将包含在内。
.exe, .dylib). For Unix-based platforms where shared
libraries typically have version details in their file name, these will also be
included.
TARGET_FILE_NAME
TARGET_FILE_NAME
TARGET_FILE但没有路径(即它仅提供文件名部分)。
TARGET_FILE but without the path (i.e. it
provides just the file name part).
TARGET_FILE_DIR
TARGET_FILE_DIR
TARGET_FILE,但没有文件名。这是获取构建最终可执行文件或库的目录的最可靠方法。当使用 Xcode、Visual Studio 或 Ninja Multi-Config 等多配置生成器时,它的值对于不同的构建配置是不同的。
TARGET_FILE but without the file name. This is
the most robust way to obtain the directory in which the final executable or
library is built. Its value is different for different build configurations
when using a multi configuration generator like Xcode, Visual Studio or
Ninja Multi-Config.
在定义自定义构建规则以在构建后步骤中复制文件时,上述三个TARGET_FILE表达式特别有用(请参阅
第 18.2 节“向现有目标添加构建步骤”)。除了
TARGET_FILE表达式之外,CMake 还提供了一些特定于库的表达式,它们具有类似的作用,只是它们处理文件名前缀和/或后缀详细信息略有不同。这些表达式的名称以
TARGET_LINKER_FILEand开头TARGET_SONAME_FILE,并且往往不像表达式那样频繁使用TARGET_FILE。
The above three TARGET_FILE expressions are especially useful when defining
custom build rules for copying files around in post build steps (see
Section 18.2, “Adding Build Steps To An Existing Target”). In addition to the
TARGET_FILE expressions, CMake also provides some library-specific
expressions which have similar roles except they handle file name prefix and/or
suffix details slightly differently. These expressions have names starting with
TARGET_LINKER_FILE and TARGET_SONAME_FILE and tend not to be used as
frequently as the TARGET_FILE expressions.
CMake 3.15 添加了对其他目标相关生成器表达式的支持,这些表达式提取目标相关文件名的基本名称、前缀和后缀。需要这些更细粒度细节的项目应查阅 CMake 文档,但这种需求应该很少见。
CMake 3.15 added support for additional target-related generator expressions that extract the basename, prefix and suffix of target-related file names. Projects needing these more fine-grained details should consult the CMake documentation, but such a need should be rare.
支持 Windows 平台的项目还可以获得有关PDB
给定目标的文件的详细信息。同样,这些主要用于自定义构建任务。以 开头的表达式遵循TARGET_PDB_FILE与 for 类似的模式,提供
用于使用生成器表达式的目标的文件的TARGET_PROPERTY路径和文件名详细信息。PDB
Projects supporting the Windows platform can also obtain details about PDB
files for a given target. Again, these would mostly find use in custom build
tasks. Expressions starting with TARGET_PDB_FILE follow an analogous pattern
as for TARGET_PROPERTY, providing path and file name details for the PDB
file used for the target on which the generator expression is being used.
另一种与目标相关的生成器表达式值得特别提及。CMake 允许将库目标定义为对象库,这意味着它不是通常意义上的库,它只是 CMake 与目标关联的对象文件的集合,但实际上并不会生成最终的库文件正在被创建。使用 CMake 3.11 或更早版本时,无法链接到对象库。相反,必须以与
添加源相同的方式将对象库添加到目标。然后,CMake 在链接阶段包含这些对象文件,就像通过编译该目标源创建的对象文件一样。这是使用生成器表达式完成的
,该表达式以适合或消耗$<TARGET_OBJECTS:…>的形式列出对象文件,如以下示例所示:add_executable()add_library()
One other generator expression relating to targets deserves special mention.
CMake allows a library target to be defined as an object library, meaning it
isn’t a library in the usual sense, it is just a collection of object files
that CMake associates with a target but doesn’t actually result in a final
library file being created. When using CMake 3.11 or earlier, it is not
possible to link to an object library.
Instead, object libraries have to be added to targets in the same way that
sources are added.
CMake then includes those object files at the link stage just like the
object files created by compiling that target’s sources. This is done using the
$<TARGET_OBJECTS:…> generator expression which lists the object files in a
form suitable for add_executable() or add_library() to consume, as
demonstrated by the following example:
# Define an object library
add_library(ObjLib OBJECT src1.cpp src2.cpp)
# Define two executables which each have their own source
# file as well as the object files from ObjLib
add_executable(App1 app1.cpp $<TARGET_OBJECTS:ObjLib>)
add_executable(App2 app2.cpp $<TARGET_OBJECTS:ObjLib>)# Define an object library
add_library(ObjLib OBJECT src1.cpp src2.cpp)
# Define two executables which each have their own source
# file as well as the object files from ObjLib
add_executable(App1 app1.cpp $<TARGET_OBJECTS:ObjLib>)
add_executable(App2 app2.cpp $<TARGET_OBJECTS:ObjLib>)
在上面的示例中,没有为 单独创建库ObjLib,但
src1.cpp和src2.cpp源文件仍然只编译一次。这对于某些构建来说会更方便,因为它可以避免创建静态库的构建时间成本或动态库的符号解析的运行时间成本,但仍然避免多次编译相同的源。
In the above example, no separate library is created for ObjLib, but the
src1.cpp and src2.cpp source files are still only compiled once. This can
be more convenient for some builds because it can avoid the build time cost of
creating a static library or the run time cost of symbol resolution for a
dynamic library, yet still avoid having to compile the same sources multiple
times.
从 CMake 3.12 开始,可以直接链接到对象库,而不是$<TARGET_OBJECTS:…>如上所述使用。这种链接有一些限制,其详细信息将在
第 17.2 节“库”中讨论。
From CMake 3.12, it is possible to link directly to an object library instead
of using $<TARGET_OBJECTS:…> as outlined above.
There are limitations to such linking, details of which are discussed in
Section 17.2, “Libraries”.
生成器表达式可以提供的信息不仅仅是目标的信息。可以获得有关正在使用的编译器、构建目标的平台、构建配置的名称等信息。此类表达式往往在更高级的情况下使用,例如处理自定义编译器或解决特定编译器或工具链的特定问题。这些表达式还会导致误用,因为它们似乎提供了一种方法来执行诸如构造事物路径之类的操作,而这些操作原本可以使用更强大的方法(例如使用TARGET_FILE
表达式或其他 CMake 功能)来获得。开发人员在依赖更通用的信息生成器表达式作为解决问题的方法之前应该仔细考虑。也就是说,其中一些表达方式确实有有效的用途。这里列出了一些更常见的内容,作为进一步阅读的起点:
Generator expressions can provide information about more than just targets.
Information can be obtained about the compiler being used, the platform for
which the target is being built, the name of the build configuration and more.
These sort of expressions tend to find use in more advanced situations such as
handling a custom compiler or to work around a problem specific to a particular
compiler or toolchain. These expressions also invite misuse, since they may
appear to provide a way to do things like construct paths to things which could
otherwise have been obtained using more robust methods like using TARGET_FILE
expressions or other CMake features. Developers should think carefully before
relying on the more general information generator expressions as a way to solve
a problem. That said, some of these expressions do have valid uses. Some of the
more common ones are listed here as a starting point for further reading:
$<CONFIG>
$<CONFIG>
CMAKE_BUILD_TYPE变量而不是变量,因为该变量不用于多配置项目生成器(如 Xcode、Visual Studio 或 Ninja Multi-Config)。早期版本的 CMake 为此使用现已弃用的$<CONFIGURATION>表达式,但项目现在应仅使用$<CONFIG>.
CMAKE_BUILD_TYPE variable since that variable is not used on
multi configuration project generators like Xcode, Visual Studio or Ninja
Multi-Config.
Earlier versions of CMake used the now deprecated $<CONFIGURATION> expression
for this, but projects should now only use $<CONFIG>.
$<PLATFORM_ID>
$<PLATFORM_ID>
CMAKE_SYSTEM_NAME,项目应考虑在其特定情况下使用该变量是否会更简单。
CMAKE_SYSTEM_NAME variable and projects
should consider whether using that variable would be simpler in their specific
situation.
$<C_COMPILER_VERSION>,$<CXX_COMPILER_VERSION>
$<C_COMPILER_VERSION>, $<CXX_COMPILER_VERSION>
$<VERSION_xxx:…>生成器表达式来实现。例如,要
OLDCXX在 C++ 编译器版本低于 4.2.0 的情况下生成字符串,可以使用以下表达式:
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,4.2.0>:OLDCXX>
此类表达式往往仅在编译器类型已知并且项目需要以某种特殊方式处理编译器的特定行为的情况下使用。在特定情况下,它可能是一种有用的技术,但如果过于依赖此类表达式,则会降低项目的可移植性。
$<VERSION_xxx:…> generator expressions. For example, to produce the string
OLDCXX if the C++ compiler version is less than 4.2.0, the following
expression could be used:
$<$<VERSION_LESS:$<CXX_COMPILER_VERSION>,4.2.0>:OLDCXX>
Such expressions tend to be used only in situations where the type of compiler is known and a specific behavior of the compiler needs to be handled in some special way by the project. It can be a useful technique in specific situations, but it can reduce the portability of the project if it relies too heavily on such expressions.
某些生成器表达式会修改内容或替换特殊字符。下面是一些比较常用或者容易被误解的。
Some generator expressions modify content or substitute special characters. Below are some of the ones that are more commonly used or are easily misunderstood.
$<COMMA>
$<COMMA>
$<COMMA>可以使用 来防止将其解析为表达式语法的一部分。
$<COMMA> can be used instead to prevent parsing it
as part of the expression syntax.
$<SEMICOLON>
$<SEMICOLON>
$<SEMICOLON>替代方法,参数解析将不会看到原始分号字符,因此不会发生此类参数分割。
$<SEMICOLON> instead, argument parsing won’t see a raw semicolon
character, so such argument splitting will not occur.
$<LOWER_CASE:…>,$<UPPER_CASE:…>
$<LOWER_CASE:…>, $<UPPER_CASE:…>
$<STREQUAL:$<UPPER_CASE:${someVar}>,FOOBAR>
$<STREQUAL:$<UPPER_CASE:${someVar}>,FOOBAR>
$<JOIN:list,…>
$<JOIN:list,…>
list
内容…,从而有效地将列表项连接到…每个项之间。请注意,在不引用整个表达式的情况下,切勿使用此生成器表达式。这可以防止分号 inlist充当使用生成器表达式的命令的参数分隔符(有关此特定主题的更深入讨论,请参阅第 8.8 节“参数处理问题” )。还需要引用以防止该…部分中的任何空格充当参数分隔符。下面显示了错误使用此生成器表达式的一个非常常见的示例:
set(dirs here there) # dirs = here;there
# ERROR: space and ; treated as argument separators
set_target_properties(Foo PROPERTIES
CUSTOM_INC -I$<JOIN:${dirs}, -I>
)
# OK: Whole generator expression is quoted
set_target_properties(Foo PROPERTIES
CUSTOM_INC "-I$<JOIN:${dirs}, -I>"
)
list
with the … content, effectively joining the list items with … between
each one.
Note that this generator expression should never be used without quoting the
whole expression.
This prevents the semicolons in list from acting as argument separators for
the command in which the generator expression is being used
(see Section 8.8, “Problems With Argument Handling” for a deeper discussion of this
particular topic).
Quoting is also needed to prevent any spaces in the … part from acting as
argument separators.
The following shows a very common example of this generator expression being
used incorrectly:
set(dirs here there) # dirs = here;there
# ERROR: space and ; treated as argument separators
set_target_properties(Foo PROPERTIES
CUSTOM_INC -I$<JOIN:${dirs}, -I>
)
# OK: Whole generator expression is quoted
set_target_properties(Foo PROPERTIES
CUSTOM_INC "-I$<JOIN:${dirs}, -I>"
)
$<GENEX_EVAL:…>,$<TARGET_GENEX_EVAL:target,…>
$<GENEX_EVAL:…>, $<TARGET_GENEX_EVAL:target,…>
$<TARGET_PROPERTY:…>
,检索到的属性的值包含另一个生成器表达式。通常,检索到的属性不会被进一步评估以扩展它包含的任何生成器表达式,但可以使用
$<GENEX_EVAL:…>或$<TARGET_GENEX_EVAL:…>像这样强制扩展:
# Evaluate in current context
$<GENEX_EVAL:$<TARGET_PROPERTY:MY_PROP>>
# Evaluate for a specific target "foo"
$<TARGET_GENEX_EVAL:foo,$<TARGET_PROPERTY:foo,MY_PROP>>
项目应该很少需要使用这两个生成器表达式。上面的示例演示了将它们添加到 CMake 的主要动机,但这种情况通常不应出现在大多数项目中。
$<TARGET_PROPERTY:…>
and the value of the retrieved property contains another generator expression.
Normally, the retrieved property is not evaluated further to expand any
generator expressions it contains, but expansion can be forced using
$<GENEX_EVAL:…> or $<TARGET_GENEX_EVAL:…> like so:
# Evaluate in current context
$<GENEX_EVAL:$<TARGET_PROPERTY:MY_PROP>>
# Evaluate for a specific target "foo"
$<TARGET_GENEX_EVAL:foo,$<TARGET_PROPERTY:foo,MY_PROP>>
Projects should rarely need to use these two generator expressions. The above example demonstrates the primary motivation for why they were added to CMake, but that scenario should not typically arise in most projects.
与其他功能相比,生成器表达式是最近添加的 CMake 功能。因此,在线和其他地方有关 CMake 的许多材料往往不使用它们。这是不幸的,因为生成器表达式通常比旧方法更健壮并且提供更多通用性。在一些常见的示例中,善意的指导会导致逻辑仅适用于受支持的项目生成器或平台的子集,但使用合适的生成器表达式将不会导致此类限制。对于尝试为不同构建类型执行不同操作的项目逻辑来说尤其如此。因此,开发人员应该熟悉生成器表达式提供的功能。上面提到的这些表达式只是 CMake 支持的一部分,但它们为涵盖大多数开发人员可能面临的大多数情况奠定了良好的基础。
Compared to other functionality, generator expressions are a more recently added CMake feature. Because of this, much of the material online and elsewhere about CMake tends not to use them. This is unfortunate, since generator expressions are typically more robust and provide more generality than older methods. There are some common examples where well-intentioned guidance leads to logic which only works for a subset of supported project generators or platforms, but where the use of suitable generator expressions instead would result in no such limitations. This is particularly true in relation to project logic which tries to do different things for different build types. Therefore, developers should become familiar with the capabilities that generator expressions provide. Those expressions mentioned above are only a subset of what CMake supports, but they form a good foundation for covering the majority of situations most developers are likely to face.
明智地使用生成器表达式可以生成更简洁的
CMakeLists.txt文件。例如,可以相对简洁地完成根据构建类型有条件地包含源文件,如前面给出的示例所示$<CONFIG:…>。这种用法减少了 if-then-else 逻辑的数量,只要生成器表达式不太复杂,就会产生更好的可读性。生成器表达式也非常适合处理根据目标或构建类型而变化的内容。CMake 中没有其他机制能够提供相同程度的灵活性和通用性来处理可能有助于特定目标属性所需的最终内容的众多因素。
Used judiciously, generator expressions can result in more succinct
CMakeLists.txt files. For example, conditionally including a source file
depending on the build type can be done relatively concisely, as the example
given earlier for $<CONFIG:…> showed. Such uses reduce the amount of
if-then-else logic, resulting in better readability as long as the generator
expressions are not too complex. Generator expressions are also a perfect fit
for handling content that changes depending on the target or the build type.
No other mechanism in CMake offers the same degree of flexibility and
generality for handling the multitude of factors which may contribute to the
final content needed for a particular target property.
相反,很容易走极端并尝试将所有内容都变成生成器表达式。这可能会导致表达式过于复杂,最终使逻辑变得模糊并且难以调试(第 13.4 节“调试生成器表达式” 提供了一些技术来帮助解决这一问题)。与往常一样,开发人员应该优先考虑清晰性而不是聪明性,对于生成器表达式尤其如此。首先考虑 CMake 是否已经提供了专用工具来实现相同的结果。各种 CMake 模块提供了更有针对性的功能,针对特定的第三方包或执行某些特定任务。还有各种变量和属性可以简化或完全取代对生成器表达式的需求。花几分钟查阅 CMake 参考文档可以节省许多小时构建并不真正需要的复杂生成器表达式的不必要工作。
Conversely, it is easy to go overboard and to try to make everything a generator expression. This can lead to overly complex expressions which ultimately obscure the logic and which can be difficult to debug (Section 13.4, “Debugging Generator Expressions” provides some techniques to help with that). As always, developers should favor clarity over cleverness and this is especially true with generator expressions. Consider first whether CMake already provides a dedicated facility to achieve the same result. Various CMake modules provide more targeted functionality aimed at a particular third party package or for carrying out certain specific tasks. There are also a variety of variables and properties which could simplify or replace the need for generator expressions altogether. A few minutes consulting the CMake reference documentation can save many hours of unnecessary work constructing complex generator expressions which were not really needed.
前面的章节主要关注 CMake 的核心方面。变量、属性、流程控制、生成器表达式、函数等都是 CMake 语言的一部分。相比之下,模块是构建在核心语言功能之上的预构建 CMake 代码块。它们提供了一组丰富的功能,项目可以使用这些功能来实现各种目标。模块作为普通 CMake 代码编写和打包,因此易于人类阅读,也可以成为了解更多有关如何在 CMake 中完成工作的有用资源。
The preceding chapters have focused mostly on the core aspects of CMake. Variables, properties, flow control, generator expressions, functions, etc. are all part of what could be considered the CMake language. In contrast, modules are pre-built chunks of CMake code built on top of the core language features. They provide a rich set of functionality which projects can use to accomplish a wide variety of goals. Being written and packaged as ordinary CMake code and therefore being human readable, modules can also be a useful resource for learning more about how to get things done in CMake.
模块被收集在一起并作为 CMake 版本的一部分提供在单个目录中。项目以两种方式之一使用模块,直接使用或作为查找外部包的一部分。使用模块的更直接方法是使用include()命令将模块代码注入到当前作用域中。这就像第 7.2 节“include()”中已经讨论的行为一样,只是只需要为命令提供模块的基本名称include(),而不是完整路径或文件扩展名。所有选项都可以include()像以前一样工作。
Modules are collected together and provided in a single directory as part of a
CMake release. Projects employ modules in one of two ways, either directly or
as part of finding an external package. The more direct method of employing
modules uses the include() command to essentially inject the module code
into the current scope. This works just like the behavior already discussed
back in Section 7.2, “include()” except that only the base name of the module
needs to be given to the include() command, not the full path or file
extension. All of the options to include() work exactly as before.
include(module
[OPTIONAL]
[RESULT_VARIABLE myVar]
[NO_POLICY_SCOPE]
)include(module
[OPTIONAL]
[RESULT_VARIABLE myVar]
[NO_POLICY_SCOPE]
)
当给定模块名称时,该include()命令将在一组明确定义的位置中查找名称为模块名称(区分大小写)并.cmake附加的文件。例如,include(FooBar)将导致 CMake 查找名为 的文件,FooBar.cmake在 Linux 等区分大小写的系统上,文件名将foobar.cmake不匹配。
When given a module name, the include() command will look in a well-defined
set of locations for a file whose name is the name of the module
(case-sensitive) with .cmake appended. For example, include(FooBar) would
result in CMake looking for a file called FooBar.cmake and on case-sensitive
systems like Linux, file names like foobar.cmake would not match.
当查找模块的文件时,CMake 首先参考变量
CMAKE_MODULE_PATH. 假设这是一个目录列表,CMake 将按顺序搜索每个目录。将使用第一个匹配文件,或者如果没有找到匹配文件或者为CMAKE_MODULE_PATH空或未定义,CMake 将在其自己的内部模块目录中搜索。此搜索顺序允许项目通过将目录添加到CMAKE_MODULE_PATH. 一个有用的模式是将项目的模块文件收集在一个目录中,并将其添加到
CMAKE_MODULE_PATH顶级
CMakeLists.txt文件开头附近的某个位置。下面的目录结构显示了这样的安排:
When looking for a module’s file, CMake first consults the variable
CMAKE_MODULE_PATH. This is assumed to be a list of directories and CMake
will search each of these in order. The first matching file will be used,
or if no matching file is found or if CMAKE_MODULE_PATH is empty or
undefined, CMake will then search in its own internal module directory. This
search order allows projects to add their own modules seamlessly by adding
directories to CMAKE_MODULE_PATH. A useful pattern is to collect together a
project’s module files in a single directory and add it to the
CMAKE_MODULE_PATH somewhere near the beginning of the top level
CMakeLists.txt file. The following directory structure shows such an
arrangement:
相应的CMakeLists.txt文件只需将cmake
目录添加到其中CMAKE_MODULE_PATH,然后include()在加载每个模块时只需使用基本文件名即可调用。
The corresponding CMakeLists.txt file then only needs to add the cmake
directory to the CMAKE_MODULE_PATH and it can then call include() using
just the base file name when loading each of the modules.
cmake_minimum_required(VERSION 3.0)
project(Example)
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_CURRENT_SOURCE_DIR}/cmake"
)
# Inject code from project-provided modules
include(CoolThings)
include(MyModule)cmake_minimum_required(VERSION 3.0)
project(Example)
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_CURRENT_SOURCE_DIR}/cmake"
)
# Inject code from project-provided modules
include(CoolThings)
include(MyModule)
CMake 用于查找模块的搜索顺序有一个例外。如果调用的文件本身在CMake自己的内部模块目录中,那么在
查询之前include()将首先搜索内部模块目录。这可以防止项目代码意外(或故意)用自己的模块替换官方模块并更改记录的行为。CMAKE_MODULE_PATH
There is one exception to the search order used by CMake to find a module. If
the file calling include() is itself inside CMake’s own internal module
directory, then the internal module directory will be searched first before
consulting CMAKE_MODULE_PATH. This prevents project code from accidentally
(or deliberately) replacing an official module with one of their own and
changing the documented behavior.
使用模块的另一种方法是使用命令find_package()。这在第 24.5 节“查找包”中详细讨论,但目前,该命令的简化形式(不带任何可选关键字)演示了其基本用法:
The other way to employ modules is with the find_package() command. This
is discussed in detail in Section 24.5, “Finding Packages”, but for the moment, a simplified
form of that command without any of the optional keywords demonstrates its
basic usage:
find_package(PackageName)find_package(PackageName)
以这种方式使用时,行为与 CMake 非常相似,include()只是 CMake 将搜索名为FindPackageName.cmake而不是
PackageName.cmake. 这种方法通常将有关外部包的详细信息引入构建中,包括导入的目标、定义相关文件、库或程序位置的变量、有关可选组件的信息、版本详细信息等。相关的选项和功能集比所find_package()提供的要丰富得多,第 24 章“查找事物”专门详细介绍了该主题。include()
When used in this way, the behavior is very similar to include() except CMake
will search for a file called FindPackageName.cmake rather than
PackageName.cmake. This is the method by which details about an external
package are often brought into the build, including things like imported
targets, variables defining locations of relevant files, libraries or programs,
information about optional components, version details and so on. The set of
options and features associated with find_package() is considerably richer
than what is provided for include() and Chapter 24, Finding Things is dedicated to
covering the topic in detail.
本章的其余部分介绍了作为 CMake 版本的一部分包含的许多有趣的模块。这绝不是一个全面的集合,但它们确实提供了可用功能的风格。其他模块将在后续章节中介绍,它们的功能与讨论的主题密切相关。CMake 文档提供了所有可用模块的完整列表,每个模块都有自己的帮助部分,解释该模块提供的内容以及如何使用它。但要预先警告的是,文档的质量确实因模块而异。
The remainder of this chapter introduces a number of interesting modules that are included as part of a CMake release. This is by no means a comprehensive set, but they do give a flavor of the sort of functionality that is available. Other modules are introduced in subsequent chapters where their functionality is closely related to the topic of discussion. The CMake documentation provides a complete list of all available modules, each with its own help section explaining what the module provides and how it can be used. Be forewarned though that the quality of the documentation does vary from module to module.
CMake 模块涵盖的更全面的领域之一是检查各种事物的存在或支持。该系列模块的工作方式基本相同,编写少量测试代码,然后尝试编译并可能链接并运行生成的可执行文件,以确认代码中正在测试的内容是否受支持。所有这些模块的名称都以Check.
One of the more comprehensive areas covered by CMake’s modules is checking for
the existence of or support for various things. This family of modules all work
in fundamentally the same way, writing a short amount of test code and then
attempting to compile and possibly link and run the resultant executable to
confirm whether what is being tested in the code is supported. All of these
modules have a name beginning with Check.
一些更基本的Check…模块是将简短的测试文件编译并链接到可执行文件并返回成功/失败结果的模块。在 CMake 3.19 或更高版本中,该CheckSourceCompiles模块提供了此功能。它定义了check_source_compiles()命令:
Some of the more fundamental Check… modules are those that compile and
link a short test file into an executable and return a success/fail result.
With CMake 3.19 or later, the CheckSourceCompiles module provides this
capability.
It defines the check_source_compiles() command:
include(CheckSourceCompiles)
check_source_compiles(lang code resultVar
[FAIL_REGEX regexes...]
[SRC_EXT extension]
)include(CheckSourceCompiles)
check_source_compiles(lang code resultVar
[FAIL_REGEX regexes...]
[SRC_EXT extension]
)
这将是 CMake 支持的
lang语言之一,例如C、等。在早期的 CMake 版本中,单独的每种语言模块提供相同的功能,但支持的语言集要小得多。这些模块具有以下形式的名称,每个模块都提供执行测试的关联命令:CXXCUDACheck<LANG>SourceCompiles
The lang would be one of the languages CMake supports, such as C, CXX,
CUDA and so on.
With earlier CMake versions, separate per-language modules provide the same
capabilities, but the set of supported languages is much smaller.
These modules have names of the form Check<LANG>SourceCompiles and each one
provides an associated command that performs the test:
include(CheckCSourceCompiles)
check_c_source_compiles(code resultVar
[FAIL_REGEX regexes...]
)
include(CheckCXXSourceCompiles)
check_cxx_source_compiles(code resultVar
[FAIL_REGEX regexes...]
)
include(CheckFortranSourceCompiles)
check_fortran_source_compiles(code resultVar
[FAIL_REGEX regexes...]
[SRC_EXT extension]
)include(CheckCSourceCompiles)
check_c_source_compiles(code resultVar
[FAIL_REGEX regexes...]
)
include(CheckCXXSourceCompiles)
check_cxx_source_compiles(code resultVar
[FAIL_REGEX regexes...]
)
include(CheckFortranSourceCompiles)
check_fortran_source_compiles(code resultVar
[FAIL_REGEX regexes...]
[SRC_EXT extension]
)
对于所有这些命令,code参数应该是一个包含源代码的字符串,该源代码应该为相应的语言生成可执行文件。尝试编译和链接代码的结果存储在
resultVar缓存变量中,true 表示成功。根据具体情况,错误值可能是空字符串、错误消息等。执行一次测试后,后续 CMake 运行将使用缓存的结果,而不是再次执行测试。即使正在测试的代码发生更改,情况也是如此。要强制重新评估,必须手动从缓存中删除该变量。
For all of these commands, the code argument is expected to be a string
containing source code that should produce an executable for the corresponding
language.
The result of an attempt to compile and link the code is stored in
resultVar as a cache variable, with true indicating success.
False values could be an empty string, an error message, etc. depending on the
situation.
After the test has been performed once, subsequent CMake runs will use the
cached result rather than performing the test again.
This is the case even if the code being tested is changed.
To force re-evaluation, the variable has to be manually removed from the cache.
如果FAIL_REGEX指定了该选项,则应用附加条件。如果测试编译和链接的输出与任何指定的
regexes(正则表达式列表)匹配,则即使代码编译和链接成功,检查也将被视为失败。
If the FAIL_REGEX option is specified, then an additional criteria applies.
If the output of the test compilation and linking matches any of the specified
regexes (a list of regular expressions), the check will be deemed to have
failed, even if the code compiles and links successfully.
include(CheckCSourceCompiles)
check_c_source_compiles("int main() { int myVar; }"
unusedNotDetected
FAIL_REGEX "[Ww]arn"
)
if(unusedNotDetected)
message("Unused variables do not generate warnings")
endif()include(CheckCSourceCompiles)
check_c_source_compiles("int main() { int myVar; }"
unusedNotDetected
FAIL_REGEX "[Ww]arn"
)
if(unusedNotDetected)
message("Unused variables do not generate warnings")
endif()
对于 Fortran,文件扩展名会影响编译器处理源文件的方式,因此可以使用选项显式指定文件扩展名
SRC_EXT以获得预期行为。使用旧模块时,对于 C 或 C++ 情况没有等效选项
Check<LANG>SourceCompiles,但新CheckSourceCompiles
模块支持所有语言。
In the case of Fortran, the file extension can affect how compilers treat
source files, so the file extension can be explicitly specified with the
SRC_EXT option to obtain the expected behavior.
There is no equivalent option for the C or C++ cases when using the older
Check<LANG>SourceCompiles modules, but the newer CheckSourceCompiles
module supports it for all languages.
CMAKE_REQUIRED_…在调用任何编译测试命令之前可以设置许多形式的变量,以影响它们编译代码的方式:
A number of variables of the form CMAKE_REQUIRED_… can be set before
calling any of the compilation test commands to influence how they compile the
code:
CMAKE_REQUIRED_FLAGS
CMAKE_REQUIRED_FLAGS
CMAKE_<LANG>_FLAGS在相关变量
的内容之后传递到编译器命令行的附加标志CMAKE_<LANG>_FLAGS_<CONFIG>(请参见第 15.4 节“编译器和链接器变量”)。这必须是具有多个由空格分隔的标志的单个字符串,这与下面的所有其他变量是 CMake 列表不同。
CMAKE_<LANG>_FLAGS and
CMAKE_<LANG>_FLAGS_<CONFIG> variables (see Section 15.4, “Compiler And Linker Variables”).
This must be a single string with multiple flags being separated by spaces,
unlike all the other variables below which are CMake lists.
CMAKE_REQUIRED_DEFINITIONS
CMAKE_REQUIRED_DEFINITIONS
-DFOO或 的形式指定-DFOO=bar。
-DFOO or -DFOO=bar.
CMAKE_REQUIRED_INCLUDES
CMAKE_REQUIRED_INCLUDES
CMAKE_REQUIRED_LIBRARIES
CMAKE_REQUIRED_LIBRARIES
-l选项或类似选项,仅提供库名称或 CMake 导入目标的名称(在第 17 章“目标类型”中讨论)。
-l option or similar,
provide just the library name or the name of a CMake imported target (discussed
in Chapter 17, Target Types).
CMAKE_REQUIRED_LINK_OPTIONS
CMAKE_REQUIRED_LINK_OPTIONS
CMAKE_REQUIRED_QUIET
CMAKE_REQUIRED_QUIET
这些变量用于构造try_compile()内部调用的参数以执行检查。CMake 文档
try_compile()讨论了可能对检查产生影响的其他变量,而try_compile()与工具链选择和要构建的目标类型相关的行为的其他方面将在第 22.5 节“编译器检查”中介绍。
These variables are used to construct arguments to the try_compile() call
made internally to perform the check. The CMake documentation for
try_compile() discusses additional variables which may have an effect on the
checks, while other aspects of try_compile() behavior relating to toolchain
selection and the type of target to build are covered in Section 22.5, “Compiler Checks”.
除了检查代码是否可以构建之外,CMake 还提供了测试构建的可执行文件是否可以成功运行的模块。成功是通过从提供的源创建的可执行文件的退出代码来衡量的,0 被视为成功,所有其他值表示失败。使用 CMake 3.19 或更高版本,单个模块提供适用于所有语言的命令:
In addition to checking whether code can be built, CMake also provides modules that test whether the built executable can be run successfully. Success is measured by the exit code of the executable created from the source provided, with 0 being treated as success and all other values indicating failure. With CMake 3.19 or later, a single module provides a command for all languages:
include(CheckSourceRuns)
check_source_runs(lang code resultVar
[SRC_EXT extension]
)include(CheckSourceRuns)
check_source_runs(lang code resultVar
[SRC_EXT extension]
)
同样,在早期的 CMake 版本中,单独的每种语言模块提供相同的功能,但支持的语言较少:
Again, with earlier CMake versions, separate per-language modules provide the same capabilities, but with fewer supported languages:
# Supported by all CMake versions
include(CheckCSourceRuns)
check_c_source_runs(code resultVar)
include(CheckCXXSourceRuns)
check_cxx_source_runs(code resultVar)
# Requires CMake 3.14 or later
include(CheckFortranSourceRuns)
check_fortran_source_runs(code resultVar
[SRC_EXT extension]
)# Supported by all CMake versions
include(CheckCSourceRuns)
check_c_source_runs(code resultVar)
include(CheckCXXSourceRuns)
check_cxx_source_runs(code resultVar)
# Requires CMake 3.14 or later
include(CheckFortranSourceRuns)
check_fortran_source_runs(code resultVar
[SRC_EXT extension]
)
这些命令没有FAIL_REGEX选项,因为成功或失败完全取决于测试进程的退出代码。如果代码无法构建,这也被视为失败。所有相同的变量都会影响代码的构建方式
check_source_compiles(),或者check_<lang>_source_compiles()对这些模块的命令也具有相同的效果。
There is no FAIL_REGEX option for these commands, as success or failure is
determined purely by the exit code of the test process.
If the code cannot be built, this is also treated as a failure.
All the same variables that affect how the code is built for
check_source_compiles() or check_<lang>_source_compiles() also have the
same effect for these modules’ commands as well.
对于交叉编译到不同目标平台的构建,
check_source_runs()和check_<lang>_source_runs()命令的行为完全不同。如果提供了必要的详细信息,他们可能会在模拟器下运行代码,这可能会大大减慢 CMake 阶段的速度。如果尚未提供模拟器详细信息,则命令将期望通过一组变量提供预定结果,并且不会尝试运行任何内容。CMake 的命令文档中介绍了这个相当高级的主题
try_run(),这是模块命令在内部用于执行检查的内容。
For builds that are cross-compiling to a different target platform, the
check_source_runs() and check_<lang>_source_runs() commands behave quite
differently.
They may run the code under a simulator if the necessary details have been
provided, which would likely slow down the CMake stage considerably.
If simulator details have not been provided, the commands will instead expect
a predetermined result to be provided through a set of variables and will not
try to run anything.
This fairly advanced topic is covered in CMake’s documentation for the
try_run() command, which is what the module commands use internally to
perform the checks.
某些类别的检查非常常见,以至于 CMake 为它们提供了专用模块。这些消除了定义测试代码的大部分样板,并允许项目指定最少量的检查信息。这些通常只是上述模块之一提供的命令的包装器,因此用于自定义测试代码构建方式的同一组变量仍然适用。这些更专业的模块检查编译器标志、预处理器符号、函数、变量、头文件等。
Certain categories of checks are so common that CMake provides dedicated modules for them. These remove much of the boilerplate of defining the test code and allow projects to specify a minimal amount of information for the check. These are typically just wrappers around the commands provided by one of the modules mentioned above, so the same set of variables used for customizing how the test code is built still apply. These more specialized modules check compiler flags, pre-processor symbols, functions, variables, header files and more.
正如上述模块一样,CMake 3.19 或更高版本为所有支持的语言提供单个模块和命令。对于早期的 CMake 版本,必须使用一组每种语言的模块。
Just as for the modules above, CMake 3.19 or later provides a single module and command for all supported languages. For earlier CMake versions, a set of per-language modules must be used instead.
# Requires CMake 3.19 or later
include(CheckCompilerFlag)
check_compiler_flag(lang flag resultVar)# Requires CMake 3.19 or later
include(CheckCompilerFlag)
check_compiler_flag(lang flag resultVar)
# Supported by all CMake versions
include(CheckCCompilerFlag)
check_c_compiler_flag(flag resultVar)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(flag resultVar)
# Requires CMake 3.3 or later
include(CheckFortranCompilerFlag)
check_fortran_compiler_flag(flag resultVar)# Supported by all CMake versions
include(CheckCCompilerFlag)
check_c_compiler_flag(flag resultVar)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(flag resultVar)
# Requires CMake 3.3 or later
include(CheckFortranCompilerFlag)
check_fortran_compiler_flag(flag resultVar)
标志检查命令CMAKE_REQUIRED_DEFINITIONS在内部更新变量以包含在对简单测试文件的flag调用中。check_source_compiles()一组内部失败正则表达式也作为选项传递
FAIL_REGEX,测试该标志是否导致发出诊断消息。如果没有发出匹配的诊断消息,则调用结果将为真值。请注意,这意味着任何导致编译器警告但编译成功的标志仍将被视为检查失败。另请注意,这些命令假定相关CMAKE_<LANG>_FLAGS变量中已存在的任何标志(请参见第 15.4 节“编译器和链接器变量”)本身不会生成任何编译器警告。如果这样做,那么每个标志测试命令的逻辑都将失败,并且所有此类检查的结果都将失败。
The flag-checking commands update the CMAKE_REQUIRED_DEFINITIONS variable
internally to include flag in a call to check_source_compiles() with a
trivial test file.
An internal set of failure regular expressions is also passed as the
FAIL_REGEX option, testing whether the flag results in a diagnostic message
being issued or not.
The result of the call will be a true value if no matching diagnostic
message is issued.
Note that this means any flag that results in a compiler warning but successful
compilation will still be deemed to have failed the check.
Also be aware that these commands assume that any flags already present in the
relevant CMAKE_<LANG>_FLAGS variables (see Section 15.4, “Compiler And Linker Variables”)
do not themselves generate any compiler warnings.
If they do, then the logic for each of these flag-testing commands will be
defeated and the result of all such checks will be failure.
CMake 3.18 也引入了该CheckLinkerFlag模块。它提供了类似的命令check_linker_flag(),该命令主要只是一个方便的包装check_source_compiles()。因此,它支持与前面讨论的所有相同的变量,只是它接管了CMAKE_REQUIRED_LINK_OPTIONS变量的处理。
CMake 3.18 also introduced the CheckLinkerFlag module.
It provides the analogous command check_linker_flag(), which is mostly just
a convenience wrapper around check_source_compiles().
As such, it supports all the same variables as previously discussed, except
that it takes over handling of the CMAKE_REQUIRED_LINK_OPTIONS variable.
include(CheckLinkerFlag)
check_linker_flag(language flag resultVar)include(CheckLinkerFlag)
check_linker_flag(language flag resultVar)
指定的内容flag不会直接传递给链接器。链接器通过编译器调用,编译器在内部添加成功链接指定的language. 原始链接器标志通常不起作用,它通常需要某种前缀,例如-Wl,…或-Xlinker来告诉编译器将其传递给链接器。LINKER:此前缀是特定于编译器的,但可以使用特殊前缀,CMake 将自动替换正确的特定于编译器的前缀。有关相关讨论,请参见第 15.1.2 节“链接器标志”和第 15.1.3 节“目标属性命令”target_link_options()中的命令
。
The specified flag is not passed directly to the linker.
The linker is invoked via the compiler, which internally adds extra
language-specific flags, libraries, etc. needed to successfully link for the
specified language.
A raw linker flag will usually not work, it typically needs some sort of prefix
like -Wl,… or -Xlinker to tell the compiler to pass it through to the
linker.
This prefix is compiler-specific, but the special prefix LINKER: can be used
and CMake will substitute the correct compiler-specific prefix automatically.
See Section 15.1.2, “Linker Flags” and the target_link_options() command in
Section 15.1.3, “Target Property Commands” for related discussions.
include(CheckLinkerFlag)
check_linker_flag(CXX LINKER:-stats LINKER_STATS_SUPPORTED)include(CheckLinkerFlag)
check_linker_flag(CXX LINKER:-stats LINKER_STATS_SUPPORTED)
另外两个值得注意的模块是CheckSymbolExists和
CheckCXXSymbolExists。前者提供了构建测试 C 可执行文件的命令,后者与 C++ 可执行文件执行相同的操作。两者都检查特定符号是否作为预处理器符号(即可以通过语句测试的符号#ifdef)、函数或变量存在。
Two other notable modules are CheckSymbolExists and
CheckCXXSymbolExists. The former provides a command which builds a test C
executable and the latter does the same as a C++ executable. Both check whether
a particular symbol exists as either a pre-processor symbol (i.e. something
that can be tested via an #ifdef statement), a function or a variable.
include(CheckSymbolExists)
check_symbol_exists(symbol headers resultVar)
include(CheckCXXSymbolExists)
check_cxx_symbol_exists(symbol headers resultVar)include(CheckSymbolExists)
check_symbol_exists(symbol headers resultVar)
include(CheckCXXSymbolExists)
check_cxx_symbol_exists(symbol headers resultVar)
headers对于(如果需要给出多个标头,则为 CMake 列表)中指定的每一项,#include都会将相应的项添加到测试源代码中。在大多数情况下,正在检查的符号将由这些标头之一定义。测试结果resultVar以通常的方式存储在缓存变量中。
For each of the items specified in headers (a CMake list if more than one
header needs to be given), a corresponding #include will be added to the test
source code. In most cases, the symbol being checked will be defined by one of
these headers. The result of the test is stored in the resultVar cache
variable in the usual way.
对于函数和变量,符号需要解析为测试可执行文件的一部分。如果函数或变量由库提供,则必须链接该库作为测试的一部分,这可以使用变量来完成CMAKE_REQUIRED_LIBRARIES。
In the case of functions and variables, the symbol needs to resolve to
something that is part of the test executable. If the function or variable is
provided by a library, that library must be linked as part of the test, which
can be done using the CMAKE_REQUIRED_LIBRARIES variable.
include(CheckSymbolExists)
check_symbol_exists(sprintf stdio.h HAVE_SPRINTF)
include(CheckCXXSymbolExists)
set(CMAKE_REQUIRED_LIBRARIES SomeCxxSDK)
check_cxx_symbol_exists(SomeCxxInitFunc
somecxxsdk.h
HAVE_SOMECXXSDK
)include(CheckSymbolExists)
check_symbol_exists(sprintf stdio.h HAVE_SPRINTF)
include(CheckCXXSymbolExists)
set(CMAKE_REQUIRED_LIBRARIES SomeCxxSDK)
check_cxx_symbol_exists(SomeCxxInitFunc
somecxxsdk.h
HAVE_SOMECXXSDK
)
这些命令可以检查的函数和变量的种类有限制。只能使用那些满足预处理器符号命名要求的符号。对于 的影响更强烈check_cxx_symbol_exists(),因为它意味着只能检查全局命名空间中的非模板函数或变量,因为任何作用域 ( ::) 或模板标记 ( <>) 对于预处理器符号都无效。也无法区分同一函数的不同重载,因此也无法检查这些重载。
There are limitations on the sort of functions and variables that can be
checked by these commands.
Only those symbols that satisfy the naming requirements for a preprocessor
symbol can be used.
The implications are stronger for check_cxx_symbol_exists(), since it means
only non-template functions or variables in the global namespace can be
checked because any scoping (::) or template markers (<>) would not be
valid for a preprocessor symbol.
It is also impossible to distinguish between different overloads of the same
function, so these cannot be checked either.
还有其他模块旨在提供与CheckSymbolExists. 这些其他模块要么来自 CMake 的早期版本,要么适用于 C 或 C++ 以外的语言。该CheckFunctionExists模块已被记录为已弃用,并且该CheckVariableExists模块
CheckSymbolExists不提供尚未提供的任何内容。该CheckFortranFunctionExists模块对于那些使用 Fortran 的项目可能很有用,但请注意,没有CheckFortranVariableExists
模块。Fortran 项目可能希望使用 FortranCheckFortranSourceCompiles来保持一致性。
There are other modules which aim to provide functionality that is similar to
or a subset of that covered by CheckSymbolExists. These other modules are
either from earlier versions of CMake or are for a language other than C or
C++. The CheckFunctionExists module is already documented as being
deprecated and the CheckVariableExists module offers nothing that
CheckSymbolExists doesn’t already provide.
The CheckFortranFunctionExists module may be useful for those projects
working with Fortran, but note that there is no CheckFortranVariableExists
module. Fortran projects may want to use CheckFortranSourceCompiles for
consistency instead.
其他模块提供更详细的检查。例如,可以使用 测试结构成员CheckStructHasMember,可以使用 测试特定的 C 或 C++ 函数原型
CheckPrototypeDefinition,并且可以使用 测试非用户类型的大小CheckTypeSize。其他更高级别的检查也是可能的,如
CheckLanguage和CheckLibraryExists各个
CheckIncludeFile…模块所提供的。随着 CMake 的发展,进一步的检查模块会继续添加到其中,因此请查阅 CMake 模块文档以查看完整的可用功能集。
More detailed checks are provided by other modules.
For example, struct members can be tested with CheckStructHasMember,
specific C or C++ function prototypes can be tested with
CheckPrototypeDefinition and the size of non-user types can be tested
with CheckTypeSize.
Other higher level checks are also possible, as provided by
CheckLanguage, CheckLibraryExists and the various
CheckIncludeFile… modules.
Further check modules continue to be added to CMake as it evolves, so consult
the CMake module documentation to see the full set of available functionality.
在进行多次检查或执行检查的效果需要彼此隔离或与当前范围的其余部分隔离的情况下,手动保存和恢复检查之前和之后的状态可能会很麻烦。特别是各种CMAKE_REQUIRED_…
变量经常需要保存和恢复。为了帮助解决此问题,CMake 提供了CMakePushCheckState定义以下三个命令的模块:
In situations where multiple checks are being made or where the effects of
performing the checks need to be isolated from each other or from the rest of
the current scope, it can be cumbersome to manually save and restore the state
before and after the checks. In particular, the various CMAKE_REQUIRED_…
variables often need to be saved and restored. To help with this, CMake
provides the CMakePushCheckState module which defines the following
three commands:
include(CMakePushCheckState)
cmake_push_check_state([RESET])
cmake_pop_check_state()
cmake_reset_check_state()include(CMakePushCheckState)
cmake_push_check_state([RESET])
cmake_pop_check_state()
cmake_reset_check_state()
这些命令允许将各种CMAKE_REQUIRED_…变量视为一个集合,并将其状态推入虚拟堆栈或从虚拟堆栈弹出。每次cmake_push_check_state()调用时,它都会有效地为变量CMAKE_REQUIRED_…(以及CMAKE_EXTRA_INCLUDE_FILES仅由模块使用的变量
CheckTypeSize)开始一个新的虚拟变量范围。
cmake_pop_check_state()相反,它会丢弃CMAKE_REQUIRED_…变量的当前值并将它们恢复为前一个堆栈级别的值。该cmake_reset_check_state()命令可以方便地清除所有
CMAKE_REQUIRED_…变量,而RESET选项
cmake_push_check_state()也可以方便地在推送过程中清除变量。但请注意,CMake 3.10 之前存在一个错误,导致该
RESET选项被忽略,因此对于需要使用 3.10 之前版本的项目,最好使用单独的调用来
cmake_reset_check_state()代替。
These commands allow the various CMAKE_REQUIRED_… variables to be treated
as a set and to have their state pushed and popped onto/from a virtual stack.
Each time cmake_push_check_state() is called, it effectively begins a new
virtual variable scope for just the CMAKE_REQUIRED_… variables (and also
the CMAKE_EXTRA_INCLUDE_FILES variable which is only used by the
CheckTypeSize module).
cmake_pop_check_state() is the opposite, it discards the current values of
the CMAKE_REQUIRED_… variables and restores them to the previous stack
level’s values.
The cmake_reset_check_state() command is a convenience for clearing all the
CMAKE_REQUIRED_… variables and the RESET option to
cmake_push_check_state() is also just a convenience for clearing the
variables as part of the push.
Note, however, that a bug existed prior to CMake 3.10 which resulted in the
RESET option being ignored, so for projects that need to work with versions
before 3.10, it is better to use a separate call to
cmake_reset_check_state() instead.
# Start with a known state we can modify and undo later
include(CMakePushCheckState)
cmake_push_check_state()
cmake_reset_check_state()
set(CMAKE_REQUIRED_FLAGS -Wall)
include(CheckSymbolExists)
check_symbol_exists(FOO_VERSION foo/version.h HAVE_FOO)
if(HAVE_FOO)
# Preserve -Wall and add more things for extra checks
cmake_push_check_state()
set(CMAKE_REQUIRED_INCLUDES foo/inc.h foo/more.h)
set(CMAKE_REQUIRED_DEFINES -DFOOBXX=1)
check_symbol_exists(FOOBAR "" HAVE_FOOBAR)
check_symbol_exists(FOOBAZ "" HAVE_FOOBAZ)
check_symbol_exists(FOOBOO "" HAVE_FOOBOO)
cmake_pop_check_state()
# Now back to just -Wall
endif()
# Clear CMAKE_REQUIRED_... variables for this last check
cmake_reset_check_state()
check_symbol_exists(__TIME__ "" HAVE_PPTIME)
# Restore all CMAKE_REQUIRED_... variables to their
# original values from the top of this example
cmake_pop_check_state()# Start with a known state we can modify and undo later
include(CMakePushCheckState)
cmake_push_check_state()
cmake_reset_check_state()
set(CMAKE_REQUIRED_FLAGS -Wall)
include(CheckSymbolExists)
check_symbol_exists(FOO_VERSION foo/version.h HAVE_FOO)
if(HAVE_FOO)
# Preserve -Wall and add more things for extra checks
cmake_push_check_state()
set(CMAKE_REQUIRED_INCLUDES foo/inc.h foo/more.h)
set(CMAKE_REQUIRED_DEFINES -DFOOBXX=1)
check_symbol_exists(FOOBAR "" HAVE_FOOBAR)
check_symbol_exists(FOOBAZ "" HAVE_FOOBAZ)
check_symbol_exists(FOOBOO "" HAVE_FOOBOO)
cmake_pop_check_state()
# Now back to just -Wall
endif()
# Clear CMAKE_REQUIRED_... variables for this last check
cmake_reset_check_state()
check_symbol_exists(__TIME__ "" HAVE_PPTIME)
# Restore all CMAKE_REQUIRED_... variables to their
# original values from the top of this example
cmake_pop_check_state()
CMake 对某些语言(尤其是 C 和 C++)具有出色的内置支持。它还包括许多模块,以更可扩展和可配置的方式提供对语言的支持。这些模块允许通过定义相关命令、变量和属性来将某些语言或与语言相关的包的各个方面提供给项目。其中许多模块是作为find_package()调用支持的一部分提供的(请参阅
第 24.5 节“查找包”),而其他模块则旨在更直接地使用 via
include()将事物带入当前范围。以下模块列表应该可以让您了解可用的语言支持类型:
CMake has excellent built-in support for some languages, especially C and C++.
It also includes a number of modules which provide support for languages in a
more extensible and configurable way. These modules allow aspects of some
languages or language-related packages to be made available to projects by
defining relevant commands, variables and properties. Many of these
modules are provided as part of the support for find_package() calls (see
Section 24.5, “Finding Packages”), while others are intended to be used more directly via
include() to bring things into the current scope. The following module list
should give a taste of the sort of language support available:
CSharpUtilities
CSharpUtilities
FindCUDA(但请注意,在最近的 CMake 版本中,这已被对 CUDA 作为一流语言的支持所取代)
FindCUDA (but note this has been superseded by support for CUDA as a first
class language in its own right in recent CMake versions)
FindJava, FindJNI,UseJava
FindJava, FindJNI, UseJava
FindLua
FindLua
FindMatlab
FindMatlab
FindPerl,FindPerlLibs
FindPerl, FindPerlLibs
FindPython
FindPython
FindPHP4
FindPHP4
FindRuby
FindRuby
FindSWIG,UseSWIG
FindSWIG, UseSWIG
FindTCL
FindTCL
FortranCInterface
FortranCInterface
此外,还提供了用于与外部数据和项目交互的模块,第 28 章“外部内容”中深入介绍了该主题。还提供了许多模块来促进测试和打包的各个方面。它们与作为 CMake 套件一部分分发的 CTest 和 CPack 工具有着密切的关系,并在第 25 章“测试”和第 27 章“打包”中进行了深入介绍。该模块还提供调试帮助CMakePrintHelpers(请参见
第 13.2 节“打印助手”)。
In addition, modules are also provided for interacting with external data and
projects, a topic covered in depth in Chapter 28, External Content. A number of modules
are also provided to facilitate various aspects of testing and packaging. These
have a close relationship with the CTest and CPack tools distributed as part of
the CMake suite and are covered in depth in Chapter 25, Testing and Chapter 27, Packaging.
Debugging assistance is also provided by the CMakePrintHelpers module (see
Section 13.2, “Print Helpers”).
CMake 的模块集合提供了构建在核心 CMake 语言之上的丰富功能。项目可以通过在特定目录下添加自己的自定义模块,然后将该路径添加到变量来轻松扩展可用功能集CMAKE_MODULE_PATH。应该优先使用 ,
CMAKE_MODULE_PATH而不是在调用中跨复杂目录结构硬编码绝对或相对路径include(),因为这将鼓励通用 CMake 逻辑与可能应用该逻辑的位置分离。随着项目的发展,这反过来又使得将 CMake 模块重新定位到不同的目录,或者跨不同项目重用逻辑变得更容易。事实上,组织构建自己的模块集合,甚至可能将它们存储在自己单独的存储库中并不罕见。通过CMAKE_MODULE_PATH在每个项目中进行适当的设置,这些可重用的 CMake 构建块就可以根据需要广泛使用。
CMake’s collection of modules provides a wealth of functionality built on top
of the core CMake language. A project can easily extend the set of available
functionality by adding their own custom modules under a particular directory
and then adding that path to the CMAKE_MODULE_PATH variable. The use of
CMAKE_MODULE_PATH should be preferred over hard-coding absolute or relative
paths across complex directory structures in include() calls, since this will
encourage generic CMake logic to be decoupled from the places where that logic
may be applied. This in turn makes it easier to relocate CMake modules to
different directories as a project evolves, or to re-use the logic across
different projects. Indeed, it is not unusual for an organization to build up
its own collection of modules, perhaps even storing them in their own separate
repository.
By setting CMAKE_MODULE_PATH appropriately in each project, those reusable
CMake building blocks are then made available for use as widely as needed.
随着时间的推移,开发人员通常会接触到越来越多的有趣场景,CMake 模块可能会为此提供有用的快捷方式或现成的解决方案。有时,快速扫描可用模块可能会产生意想不到的隐藏宝石,或者新模块可能会为项目迄今为止以较差的方式实现的某些内容提供更好的维护实现。CMake 的模块具有潜在的大量开发人员和项目在不同的平台和情况下使用它们的优势,因此它们可能为在许多情况下执行自己的手动逻辑的项目提供更具吸引力的替代方案。然而,不同模块的质量确实有所不同。有些模块在 CMake 存在的早期就开始了它们的生命,如果不及时跟上 CMake 或这些模块相关领域的变化,这些模块有时会变得不太有用。对于模块来说尤其如此,这些Find…模块可能无法像人们希望的那样密切跟踪它们正在查找的包的较新版本。另一方面,模块只是普通的 CMake 代码,因此任何人都可以检查它们、向它们学习、改进或更新它们,而无需学习超出项目中基本 CMake 使用所需的内容。事实上,对于希望参与 CMake 本身工作的开发人员来说,它们是一个很好的起点。
Over time, a developer will typically be exposed to an increasing number of
interesting scenarios for which a CMake module may provide useful shortcuts or
ready-made solutions. Sometimes a quick scan of the available modules can yield
an unexpected hidden gem, or a new module may offer a better maintained
implementation of something a project has been implementing in an inferior way
up to that point. CMake’s modules have the benefit of a potentially large pool
of developers and projects using them across a diverse set of platforms and
situations, so they may offer a more compelling alternative to projects doing
their own manual logic in many cases. The quality does, however, vary from one
module to another. Some modules began their life quite early on in CMake’s
existence and these can sometimes become less useful if not kept up to date
with changes to CMake or to the areas those modules relate to. This can be
particularly true of Find… modules which may not track newer versions of
the packages they are finding as closely as one might like. On the other hand,
modules are just ordinary CMake code, so anyone can inspect them, learn from
them, improve or update them without having to learn much beyond what is needed
for basic CMake use in a project. In fact, they are an excellent starting point
for developers wishing to get involved with working on CMake itself.
CMake 提供的丰富的不同Check…模块可能是喜忧参半。开发人员可能会过于热心地检查各种事情,这可能会导致配置阶段变慢,从而获得有时令人怀疑的收益。通常会看到与这些检查相关的命令主导 CMake 运行的分析结果(请参阅第 13.5 节“分析 CMake 调用”)。考虑实施和维护检查的时间以及项目的复杂性方面的收益是否超过成本。有时,一些明智的检查足以覆盖最有用的情况,或者发现一个微妙的问题,否则可能会导致以后难以追踪问题。此外,如果使用任何模块Check…,目标是将检查逻辑与其可能被调用的范围隔离。CMakePushCheckState强烈建议
使用该模块,但如果对 3.10 之前的 CMake 版本的支持很重要,请避免使用该RESET选项。cmake_push_check_state()
The abundance of different Check… modules provided by CMake can be a mixed
blessing. Developers can be tempted to get too over-zealous with checking all
manner of things, which can result in slowing down the configure stage for
sometimes questionable gains.
It is common to see the commands related to these checks dominate the profiling
results of a CMake run (see Section 13.5, “Profiling CMake Calls”).
Consider whether the benefits outweigh the costs in terms of time to implement
and maintain the checks and the complexity of the project.
Sometimes a few judicious checks are sufficient for covering the most
useful cases, or to catch a subtle problem that might otherwise cause hard to
trace problems later. Furthermore, if using any of the Check… modules, aim
to isolate the checking logic from the scope in which it may be invoked. Use of
the CMakePushCheckState module is highly recommended, but avoid using the
RESET option to cmake_push_check_state() if support for CMake versions
before 3.10 is important.
当最低 CMake 版本可以设置为 3.20 或更高版本时,避免使用相对流行但现已弃用的TestBigEndian模块。该模块在 CMake 3.20 中已被弃用,取而代之的是新
CMAKE_<LANG>_BYTE_ORDER变量,该变量也在同一 CMake 版本中引入。正在使用的项目TestBigEndian应尽可能转换为新变量。
When the minimum CMake version can be set to 3.20 or later, avoid using the
relatively popular but now deprecated TestBigEndian module.
That module was deprecated in CMake 3.20 in favor of a new
CMAKE_<LANG>_BYTE_ORDER variable, which was also introduced in the same
CMake release.
Projects that are using TestBigEndian should transition to the new variable
where possible.
CMake 经过了很长一段时间的发展,引入了新功能、修复了错误并更改了某些功能的行为以解决缺陷或引入改进。虽然引入新功能不太可能会给使用 CMake 构建的现有项目带来问题,但如果项目依赖于旧行为,则任何行为更改都可能会破坏项目。因此,CMake 开发人员非常小心地确保以保留向后兼容性的方式实施更改,并为要更新到新行为的项目提供直接、受控的迁移路径。
CMake has evolved over a long period, introducing new functionality, fixing bugs and changing the behavior of certain features to address shortcomings or introduce improvements. While the introduction of new capabilities is unlikely to cause problems for existing projects built with CMake, any change in behavior has the potential to break projects if they are relying on the old behavior. For this reason, the CMake developers are careful to ensure that changes are implemented in such a way as to preserve backward compatibility and to provide a straightforward, controlled migration path for projects to be updated to the new behavior.
对应使用旧行为还是新行为的控制是通过 CMake 的策略机制完成的。一般来说,开发人员不会经常接触到策略,主要是在 CMake 对依赖旧版本行为的项目发出警告时。当开发人员转向更新的 CMake 版本时,较新的 CMake 版本有时会发出此类警告,以强调应如何更新项目以使用新行为。
This control over whether old or new behavior should be used is done through CMake’s policy mechanisms. In general, policies are not something that developers are exposed to all that often, mostly just when CMake issues a warning about the project relying on an older version’s behavior. When developers move to a more recent CMake release, the newer CMake version will sometimes issue such warnings to highlight how the project should be updated to use a new behavior.
CMake 的策略功能与命令密切相关,该命令已在第 3 章“最小项目”cmake_minimum_required()
中介绍过。此命令不仅指定项目所需的最低 CMake 版本,还设置 CMake 的行为以匹配给定版本的行为。因此,当项目以 开头时,它表示至少需要 CMake 3.2,并且该项目希望 CMake 的行为与 3.2 版本类似。这让项目充满信心,开发人员应该能够在方便时更新到任何较新版本的 CMake,并且项目仍将像以前一样构建。cmake_minimum_required(VERSION 3.2)
CMake’s policy functionality is closely tied to the cmake_minimum_required()
command, which was introduced back in Chapter 3, A Minimal Project. Not only does this
command specify the minimum CMake version a project requires, it also sets
CMake’s behavior to match that of the version given. Thus, when a project
starts with cmake_minimum_required(VERSION 3.2), it says that at least CMake
3.2 is needed and also that the project expects CMake to behave like the 3.2
release. This gives projects confidence that developers should be able to
update to any newer version of CMake at their convenience and the project will
still build as it did before.
然而,有时,项目可能需要比命令提供的更细粒度的控制
cmake_minimum_required()。考虑以下场景:
Sometimes, however, a project may want more fine-grained control than what the
cmake_minimum_required() command provides. Consider the following scenarios:
这些是一些常见的示例,仅由命令提供的高级控制
cmake_minimum_required()是不够的。通过该命令可以对策略进行更具体的控制cmake_policy(),该命令具有多种以不同粒度起作用的形式。在最粗层次上起作用的形式与 密切相关cmake_minimum_required():
These are some common examples where the high level control provided by the
cmake_minimum_required() command alone is not enough. More specific control
over policies is enabled through the cmake_policy() command, which has a
number of forms acting at different degrees of granularity. The form acting at
the coarsest level is a close relative to cmake_minimum_required():
cmake_policy(VERSION major[.minor[.patch[.tweak]]])cmake_policy(VERSION major[.minor[.patch[.tweak]]])
在此形式中,该命令更改 CMake 的行为以匹配指定版本的行为。该cmake_minimum_required()命令隐式调用此表单来设置 CMake 的行为。两者在很大程度上可以互换,除了项目的顶部,其中强制调用cmake_minimum_required()以强制执行最低 CMake 版本。除了顶级
CMakeLists.txt文件的开头之外,cmake_policy()当项目需要为项目的一部分强制执行特定版本的行为时,使用通常可以更清楚地传达意图,如以下示例所示:
In this form, the command changes CMake’s behavior to match that of the
specified version. The cmake_minimum_required() command implicitly calls this
form to set CMake’s behavior. The two are largely interchangeable except for
the top of the project where a call to cmake_minimum_required() is mandatory
to enforce a minimum CMake version. Apart from the start of the top level
CMakeLists.txt file, using cmake_policy() generally communicates the intent
more clearly when a project needs to enforce a particular version’s behavior
for a section of the project, as demonstrated by the following example:
cmake_minimum_required(VERSION 3.7)
project(WithLegacy)
# Uses recent CMake features
add_subdirectory(modernDir)
# Imported from another project, relies on old behavior
cmake_policy(VERSION 2.8.11)
add_subdirectory(legacyDir)cmake_minimum_required(VERSION 3.7)
project(WithLegacy)
# Uses recent CMake features
add_subdirectory(modernDir)
# Imported from another project, relies on old behavior
cmake_policy(VERSION 2.8.11)
add_subdirectory(legacyDir)
CMake 3.12 以向后兼容的方式扩展了此功能,允许项目选择性地指定版本范围而不是单个版本到cmake_minimum_required()或cmake_policy(VERSION)。该范围是使用最小版本和最大版本之间的三个点指定的,...没有空格。该范围指示使用的 CMake 版本必须至少是最低版本,并且要使用的行为应该是指定的最大值和正在运行的 CMake 版本中的较小者。这使得该项目可以有效地表达“我至少需要 CMake X,但我可以安全地使用 CMake Y 之前的策略”。以下示例显示了项目仅需要 CMake 3.7,但仍然支持 CMake 3.12 之前的所有策略的较新行为(如果正在运行的 CMake 版本支持)的两种方法:
CMake 3.12 extends this capability in a backward-compatible way by optionally
allowing the project to specify a version range rather than a single version
to either cmake_minimum_required() or cmake_policy(VERSION). The range is
specified using three dots ... between the minimum and maximum version with
no spaces. The range indicates that the CMake version in use must be at least
the minimum and the behavior to use should be the lesser of the specified
maximum and the running CMake version. This allows the project to effectively
say "I need at least CMake X, but I am safe to use with policies from up to
CMake Y". The following example shows two ways for a project to require only
CMake 3.7, but still support the newer behavior for all policies up to CMake
3.12 if the running CMake version supports them:
cmake_minimum_required(VERSION 3.7...3.12)
cmake_policy(VERSION 3.7...3.12)cmake_minimum_required(VERSION 3.7...3.12)
cmake_policy(VERSION 3.7...3.12)
3.12 之前的 CMake 版本实际上只会看到一个版本号,并且会忽略该...3.12部分,而 3.12 及更高版本会将其理解为一个范围。
CMake versions before 3.12 would effectively see just a single version number
and would ignore the ...3.12 part, whereas 3.12 and later would understand it
to mean a range.
CMake 还提供了通过以下形式单独控制每个行为更改的能力SET:
CMake also provides the ability to control each behavior change individually
with the SET form:
cmake_policy(SET CMPxxxx NEW)
cmake_policy(SET CMPxxxx OLD)cmake_policy(SET CMPxxxx NEW)
cmake_policy(SET CMPxxxx OLD)
每个单独的行为改变都有其自己的策略编号,其形式为
CMPxxxx,其中xxxx始终为四位数字。通过指定NEW或OLD,项目告诉 CMake 使用该特定策略的新或旧行为。CMake 文档提供了完整的策略列表,以及每个策略的旧行为和新行为的解释。
Each individual behavior change is given its own policy number of the form
CMPxxxx, where xxxx is always four digits. By specifying NEW or OLD, a
project tells CMake to use the new or old behavior for that particular policy.
The CMake documentation provides the full list of policies, along with an
explanation of the OLD and NEW behavior of each one.
例如,在 3.0 版本之前,CMake 允许项目使用
get_target_property()不存在的目标名称进行调用。在这种情况下,属性的值被返回而-NOTFOUND不是发出错误,但项目很可能包含不正确的逻辑。因此,从版本 3.0 开始,如果遇到这种情况,CMake 将停止并出现错误。如果项目依赖于旧的行为,它可以使用CMP0045如下策略继续这样做:
As an example, before version 3.0, CMake allowed a project to call
get_target_property() with the name of a target that didn’t exist. In such a
case, the value of the property was returned as -NOTFOUND rather than issuing
an error, but in all likelihood, the project probably contained incorrect
logic. Therefore, from version 3.0 onward, CMake halts with an error if such a
situation is encountered. In the event that a project was relying on the old
behavior, it could continue to do so using policy CMP0045 like so:
# Allow non-existent target with get_target_property()
cmake_policy(SET CMP0045 OLD)
# Would halt with an error without the above policy change
get_target_property(outVar notExist COMPILE_DEFINITIONS)# Allow non-existent target with get_target_property()
cmake_policy(SET CMP0045 OLD)
# Would halt with an error without the above policy change
get_target_property(outVar notExist COMPILE_DEFINITIONS)
制定政策的需要NEW并不常见。一种情况是项目想要设置较低的最低 CMake 版本,但如果使用更高版本,仍然可以利用更高版本的功能。例如,在 CMake 3.2 中,
CMP0055引入了策略来严格检查命令的使用情况break()
。如果项目仍然希望支持使用早期 CMake 版本进行构建,则在使用更高版本的 CMake 运行时必须显式启用附加检查。
The need for setting a policy to NEW is less common. One situation is where a
project wants to set a low minimum CMake version, but still take advantage of
later features if a later version is used. For example, in CMake 3.2, policy
CMP0055 was introduced to provide strict checking on usage of the break()
command. If the project still wanted to support being built with earlier CMake
versions, then the additional checks would have to be explicitly enabled when
run with later CMake versions.
cmake_minimum_required(VERSION 3.0)
project(PolicyExample)
if(CMAKE_VERSION VERSION_GREATER 3.1)
# Enable stronger checking of break() command usage
cmake_policy(SET CMP0055 NEW)
endif()cmake_minimum_required(VERSION 3.0)
project(PolicyExample)
if(CMAKE_VERSION VERSION_GREATER 3.1)
# Enable stronger checking of break() command usage
cmake_policy(SET CMP0055 NEW)
endif()
测试CMAKE_VERSION变量是确定策略是否可用的一种方法,但该if()命令提供了一种使用
if(POLICY…)表单的更直接的方法。上述也可以这样实现:
Testing the CMAKE_VERSION variable is one way of determining whether a policy
is available, but the if() command provides a more direct way using the
if(POLICY…) form. The above could alternatively be implemented like so:
cmake_minimum_required(VERSION 3.0)
project(PolicyExample)
# Only set the policy if the version of CMake being used
# knows about that policy number
if(POLICY CMP0055)
cmake_policy(SET CMP0055 NEW)
endif()cmake_minimum_required(VERSION 3.0)
project(PolicyExample)
# Only set the policy if the version of CMake being used
# knows about that policy number
if(POLICY CMP0055)
cmake_policy(SET CMP0055 NEW)
endif()
还可以获得特定策略的当前状态。可能需要读取当前策略设置的主要情况是在模块文件中,该文件可能是由 CMake 本身或项目提供的。然而,项目模块根据策略设置改变其行为是不寻常的。
It is also possible to get the current state of a particular policy. The main situation where the current policy setting may need to be read is in a module file, which may be one provided by CMake itself or by the project. It would be unusual, however, for a project module to change its behavior based on a policy setting.
cmake_policy(GET CMPxxxx outVar)cmake_policy(GET CMPxxxx outVar)
存储的值outVar将为OLD,NEW或为空。和
cmake_minimum_required(VERSION…)命令cmake_policy(VERSION…)重置所有策略的状态。在指定 CMake 版本或更早版本引入的那些策略将重置为NEW. 在指定版本之后添加的那些策略将有效地重置为空。
The value stored in outVar will be OLD, NEW or empty. The
cmake_minimum_required(VERSION…) and cmake_policy(VERSION…) commands
reset the state of all policies. Those policies introduced at the specified
CMake version or earlier are reset to NEW. Those policies that were added
after the specified version will effectively be reset to empty.
如果 CMake 检测到项目正在执行的操作依赖于旧行为、与新行为冲突或行为不明确,则如果相关策略未设置,它可能会发出警告。这些警告是开发人员接触 CMake 策略功能的最常见方式。它们被设计为嘈杂但信息丰富,鼓励开发人员更新项目以适应新的行为。在某些情况下,即使已明确设置策略,也可能会发出弃用警告,但这通常仅适用于已被记录为已弃用很长时间(许多版本)的策略。
If CMake detects that the project is doing something that either relies on the old behavior, conflicts with the new behavior or whose behavior is ambiguous, it may warn if the relevant policy is unset. These warnings are the most common way developers are exposed to CMake’s policy functionality. They are designed to be noisy but informative, encouraging developers to update the project to the new behavior. In some cases, a deprecation warning may be issued even if the policy has been explicitly set, but this is typically only for a policy that has already been documented as deprecated for a long time (many releases).
有时,政策警告无法立即得到解决,但这些警告可能是不受欢迎的。处理此问题的首选方法是将策略显式设置为所需的行为(OLD或NEW),这会停止警告。但这并不总是可能的,例如当项目的更深层部分发出自己的对cmake_minimum_required(VERSION…)或 的
调用cmake_policy(VERSION…),从而重置策略状态时。作为解决此类情况的临时方法,CMake 提供了
CMAKE_POLICY_DEFAULT_CMPxxxx和CMAKE_POLICY_WARNING_CMPxxxx变量,其中xxxx是通常的四位数保单编号。这些并不是由项目设置的,而是由开发人员作为缓存变量临时设置,以启用/禁用警告或检查项目是否在启用特定策略的情况下发出警告。最终,长期解决方案是解决警告强调的根本问题。然而,项目有时可能需要设置这些变量之一来消除已知无害的警告。
Sometimes the policy warnings cannot be addressed immediately, but the warnings
could be undesirable. The preferred way to handle this is to explicitly set the
policy to the desired behavior (OLD or NEW), which stops the warning. This
isn’t always possible though, such as when a deeper part of the project issues
its own call to cmake_minimum_required(VERSION…) or
cmake_policy(VERSION…), thereby resetting the policy states. As a temporary
way to work around such situations, CMake provides the
CMAKE_POLICY_DEFAULT_CMPxxxx and CMAKE_POLICY_WARNING_CMPxxxx variables
where xxxx is the usual four-digit policy number. These are not intended to
be set by the project, but rather by the developer as a cache variable
temporarily to enable/disable a warning or to check whether the project issues
warnings with a particular policy enabled. Ultimately, the long term solution
is to address the underlying problem highlighted by the warning. Nevertheless,
it may occasionally be appropriate for a project to set one of these variables
to silence a warning known to not be harmful.
有时,策略设置只需要应用于文件的特定部分。CMake 提供了一个策略堆栈,可用于简化此过程,而不是要求项目手动保存要临时更改的任何策略的现有值:
Sometimes a policy setting only needs to be applied to a specific section of a file. Rather than requiring a project to manually save the existing value of any policies it wants to change temporarily, CMake provides a policy stack which can be used to simplify this process:
cmake_policy(PUSH)
cmake_policy(POP)cmake_policy(PUSH)
cmake_policy(POP)
所有策略的现有状态可以通过操作保存PUSH,并通过相应的操作丢弃当前状态POP。每个PUSH都需要最终有一个匹配POP。在此期间,项目可以修改它需要的任何策略的设置,而无需先显式保存每个策略。同样,模块文件是可能像这样操作策略堆栈的更常见位置之一。一个简单的例子可能是一个模块文件,它临时设置了一些策略,如下所示:
The existing state of all policies can be saved with a PUSH operation and the
current state discarded with a corresponding POP. Every PUSH is required to
eventually have a matching POP. In between, the project can modify the
settings of any policies it needs to without having to explicitly save each one
first. Again, module files are one of the more common places where the policy
stack might be manipulated like this. A simple example might be a module file
which sets a few policies temporarily like so:
# Save existing policy state
cmake_policy(PUSH)
# Set some policies to OLD to preserve a few old behaviors
# CMP0060: Library path linking behavior
# CMP0021: Tolerate relative INCLUDE_DIRECTORIES
cmake_policy(SET CMP0060 OLD)
cmake_policy(SET CMP0021 OLD)
# Do various processing here...
# Restore earlier policy settings
cmake_policy(POP)# Save existing policy state
cmake_policy(PUSH)
# Set some policies to OLD to preserve a few old behaviors
# CMP0060: Library path linking behavior
# CMP0021: Tolerate relative INCLUDE_DIRECTORIES
cmake_policy(SET CMP0060 OLD)
cmake_policy(SET CMP0021 OLD)
# Do various processing here...
# Restore earlier policy settings
cmake_policy(POP)
某些命令会隐式地将新的策略状态推入堆栈,并在稍后在明确定义的点再次将其弹出。一个示例是,该add_subdirectory()
命令在进入指定的子目录时将新的策略范围压入堆栈,并在命令返回时再次将其弹出。该
include()命令执行类似的操作,在开始处理指定文件之前推送新的策略范围,并在该文件处理完成时再次弹出它。该find_package()命令还执行与 类似的操作include(),分别在开始和完成其关联FindXXX.cmake模块文件的处理时推送和弹出。
Some commands implicitly push a new policy state onto the stack and pop it
again at a well defined point later. One example is the add_subdirectory()
command which pushes a new policy scope onto the stack upon entering the
specified subdirectory and pops it again when the command returns. The
include() command does a similar thing, pushing a new policy scope before
starting to process the specified file and popping it again when processing of
that file is completed. The find_package() command also does a similar thing
to include(), pushing and popping upon starting and finishing processing of
its associated FindXXX.cmake module file respectively.
和命令还支持
防止策略堆栈自动推送弹出的选项(include()没有此类选项)。在 CMake 的早期版本中,
不会自动在策略堆栈上推送和弹出条目。添加该选项是为了让使用更高版本 CMake 的项目恢复到项目特定部分的旧行为,但通常不鼓励使用它,对于新项目来说应该没有必要。find_package()NO_POLICY_SCOPEadd_subdirectory()include()find_package()NO_POLICY_SCOPE
The include() and find_package() commands also support a NO_POLICY_SCOPE
option which prevents the automatic push-pop of the policy stack
(add_subdirectory() has no such option). In very early versions of CMake,
include() and find_package() did not automatically push and pop an entry on
the policy stack. The NO_POLICY_SCOPE option was added as a way for projects
using later CMake versions to revert back to the old behavior for specific
parts of the project, but its use is generally discouraged and should be
unnecessary for new projects.
如果可能,项目应该更喜欢使用 CMake 版本级别的策略,而不是操纵特定的策略。设置策略以匹配特定 CMake 版本的行为使项目更易于理解和更新,而对单个策略的更改可能更难以通过多个目录级别进行跟踪,特别是因为它们与版本级策略更改交互,而版本级策略更改始终会被重置。
Where possible, projects should prefer to work with policies at the CMake version level rather than manipulating specific policies. Setting policies to match a particular CMake release’s behavior makes the project easier to understand and update, whereas changes to individual policies can be harder to trace through multiple directory levels, especially because of their interaction with version-level policy changes where they are always reset.
在选择如何指定要遵循的 CMake 版本时,通常会选择cmake_minimum_required(VERSION)后者cmake_policy(VERSION)。两个主要的例外是在项目顶级CMakeLists.txt文件的开头和可以在多个项目中重复使用的模块文件的顶部。对于后一种情况,最好使用cmake_minimum_required(VERSION),因为使用该模块的项目可能会强制执行自己的最低 CMake 版本,但该模块可能有自己特定的最低版本要求。除了这些情况之外,cmake_policy(VERSION)通常会更清楚地表达意图,但从政策角度来看,这两个命令都会有效地实现相同的目标。
When choosing how to specify the CMake version to conform to, the choice
between cmake_minimum_required(VERSION) and cmake_policy(VERSION) would
usually fall to the latter. The two main exceptions to this are at the
start of the project’s top level CMakeLists.txt file and at the top of a
module file that could be re-used across multiple projects. For the latter
case, it is preferable to use cmake_minimum_required(VERSION) because the
projects using the module may enforce their own minimum CMake version, but the
module may have specific minimum version requirements of its own. Aside from
these cases, cmake_policy(VERSION) usually expresses the intent more
clearly, but both commands will effectively achieve the same thing from a
policy perspective.
如果项目确实需要操纵特定策略,则应使用if(POLICY…)而不是测试CMAKE_VERSION变量来检查该策略是否可用。这会提高代码的一致性。比较以下两种设置策略行为的方法,并注意检查和执行如何使用一致的方法:
In cases where a project does need to manipulate a specific policy, it should
check whether the policy is available using if(POLICY…) rather than testing
the CMAKE_VERSION variable. This leads to greater consistency of the code.
Compare the following two ways of setting policy behavior and note how the
check and the enforcement use a consistent approach:
# Version-level policy enforcement
if(NOT CMAKE_VERSION VERSION_LESS 3.4)
cmake_policy(VERSION 3.4)
endif()
# Individual policy-level enforcement
if(POLICY CMP0055)
cmake_policy(SET CMP0055 NEW)
endif()# Version-level policy enforcement
if(NOT CMAKE_VERSION VERSION_LESS 3.4)
cmake_policy(VERSION 3.4)
endif()
# Individual policy-level enforcement
if(POLICY CMP0055)
cmake_policy(SET CMP0055 NEW)
endif()
如果项目需要在本地操作多个单独的策略,请在该部分周围调用cmake_policy(PUSH)和 ,cmake_policy(POP)以确保范围的其余部分与更改隔离。请特别注意return()退出该代码部分的任何可能的语句,并确保在没有相应弹出的情况下不会留下任何推送。另请注意
add_subdirectory(),include()所有find_package()推送和弹出都会自动在策略堆栈上添加条目,因此不需要显式推送和弹出,除非需要在本地更改策略设置以用于拉入的文件的一小部分。项目应避免使用NO_POLICY_SCOPE关键字这些命令,因为它仅用于解决早期 CMake 版本的行为变化,并且其使用很少适合新项目。
If a project needs to manipulate multiple individual policies locally, surround
that section with calls to cmake_policy(PUSH) and cmake_policy(POP) to
ensure that the rest of the scope is isolated from the changes. Pay special
attention to any possible return() statements that exit that section of code
and ensure no push is left without a corresponding pop. Note also that
add_subdirectory(), include() and find_package() all push and pop an
entry on the policy stack automatically, so no explicit push and pop is needed
unless policy settings need to be changed locally for a small section of the
file being pulled in. Projects should avoid the NO_POLICY_SCOPE keyword of
these commands, as it is intended only for addressing a change in behavior of
very early CMake versions and its use is rarely appropriate for new projects.
旨在避免修改函数内部的策略设置。由于函数不会引入新的策略范围,因此如果未使用适当的推送-弹出逻辑正确隔离更改,则策略更改可能会影响调用者。此外,函数实现的策略设置取自函数定义的范围,而不是调用函数的范围。因此,最好在定义函数的范围内而不是在函数本身内调整任何策略设置。
Aim to avoid modifying policy settings inside a function. Since functions do not introduce a new policy scope, a policy change can affect the caller if the change is not properly isolated using the appropriate push-pop logic. Furthermore, the policy settings for the function implementation are taken from the scope in which the function was defined, not the one from which it is called. Therefore, prefer to adjust any policy settings in the scope that defines the function rather than within the function itself.
作为最后的手段,CMAKE_POLICY_DEFAULT_CMPxxxx和
CMAKE_POLICY_WARNING_CMPxxxx变量可能允许开发人员或项目解决一些特定的与政策相关的情况。开发人员可以使用这些来临时更改特定策略设置的默认值或防止出现有关特定策略的警告。项目通常应避免设置这些变量,以便开发人员在本地进行控制,但在某些情况下,即使通过调用 或 ,它们也可用于确保有关特定策略的行为或警告仍然cmake_minimum_required()存在
cmake_policy(VERSION)。在可能的情况下,项目应该尝试更新到较新的行为,而不是依赖这些变量。
As a last resort, the CMAKE_POLICY_DEFAULT_CMPxxxx and
CMAKE_POLICY_WARNING_CMPxxxx variables may allow a developer or project to
work around some specific policy-related situations. Developers may use these
to temporarily change a specific policy setting’s default or to prevent
warnings about a particular policy. Projects should generally avoid setting
these variables so that developers have control locally, but in certain
situations, they can be used to ensure the behavior or warning about a
particular policy persists even through calls to cmake_minimum_required() or
cmake_policy(VERSION). Where possible, projects should instead try to update
to the newer behavior rather than relying on these variables.
当构建运行良好时,用户往往不会过多关注 CMake 生成的输出。然而,对于从事项目的开发人员来说,诊断输出和调试功能至关重要。CMake 始终提供基本的打印功能,但版本 3.15 至 3.18 中添加的增强功能显着扩展了可用功能。
When a build is behaving well, users tend not to pay much attention to the output generated by CMake. For developers working on a project, however, diagnostic output and debugging capabilities are essential. CMake has always provided basic printing functionality, but enhancements added in versions 3.15 to 3.18 significantly extended the available capabilities.
CMake 始终支持使用该命令记录任意文本,该命令在第 5.6 节“打印变量值”message()
中进行了简要介绍。该命令的更一般形式是:
CMake has always supported logging arbitrary text using the message()
command, which was introduced briefly back in Section 5.6, “Printing Variable Values”.
The more general form of that command is:
message([mode] msg1 [msg2]...)message([mode] msg1 [msg2]...)
msg如果指定多个,它们将连接成一个不带分隔符的字符串。要保留空格、分号或换行符,请用引号将消息引起来(有关原因的详细说明,请参阅第 8.8 节“参数处理问题” )。
If more than one msg is specified, they will be joined into a single
string with no separators.
To preserve spaces, semicolons or newlines, surround the message with quotes
(see Section 8.8, “Problems With Argument Handling” for a detailed explanation of why).
消息输出可能会受到可选mode参数、cmake
命令行选项和调用时一些变量值的影响。接下来的几小节将详细介绍这些内容。
The message output can be affected by the optional mode argument, cmake
command-line options and the value of a few variables at the time of the call.
The next few subsections cover these in detail.
该message()命令接受一个可选mode关键字,该关键字提供有关所提供消息类型的信息。它会影响消息的输出方式和位置、是否输出,并且在某些情况下可能会停止该 CMake 运行的进一步处理。mode按重要性排列的公认价值观是:
The message() command accepts an optional mode keyword which provides
information about the type of message being provided.
It affects how and where the message is output, whether it is output at all and
in some cases can halt further processing for that CMake run.
Recognized mode values in order of importance are:
FATAL_ERROR
FATAL_ERROR
message()命令的位置。
message() command.
SEND_ERROR
SEND_ERROR
FATAL_ERROR除了处理将继续,直到配置阶段完成,但不会执行生成。这可能会让用户感到非常困惑,因此项目应该避免这种模式,而更愿意使用这种模式FATAL_ERROR。
FATAL_ERROR except processing will continue until
the configure stage completes, but generation will not be performed.
This can be quite confusing for users, so projects should avoid this mode and
prefer to use FATAL_ERROR instead.
WARNING
WARNING
message()日志通常还会记录发出警告的命令的位置。处理将继续。
message() command raising the warning.
Processing will continue.
AUTHOR_WARNING
AUTHOR_WARNING
WARNING,但仅在启用开发人员警告时显示(使用命令行-Wno-dev上的选项cmake来禁用它们)。项目不经常使用这种特定类型的消息,它们通常由 CMake 本身生成。
WARNING, but only shown if developer warnings are
enabled (use the -Wno-dev option on the cmake command line to disable
them). Projects do not often use this particular type of message, they are
usually generated by CMake itself.
DEPRECATION
DEPRECATION
CMAKE_ERROR_DEPRECATED变量设置为 true,则该消息将被视为错误。如果该CMAKE_WARN_DEPRECATED变量设置为 true,则该消息将被视为警告。如果两个变量均未设置,则该消息将在 CMake 3.5 或更高版本中显示,而在早期版本中隐藏。
CMAKE_ERROR_DEPRECATED variable is set to true, the message will
be treated as an error.
If the CMAKE_WARN_DEPRECATED variable is set to true, the message will be
treated as a warning.
If neither variable is set, the message will be shown for CMake 3.5 or
later and hidden for earlier versions.
NOTICE
NOTICE
STATUS
STATUS
NOTICE纯粹的信息性消息。
NOTICE for purely informational
messages.
VERBOSE
VERBOSE
DEBUG
DEBUG
TRACE
TRACE
STATUS到level的消息TRACE将打印到stdout,而NOTICE和 以上的消息将打印到stderr。这可能会导致不同日志级别的消息有时在输出中出现无序。此外,消息stderr通常暗示用户应该调查的问题或某些内容,因此NOTICE对于不需要后续操作的纯粹信息性消息来说通常是一个糟糕的选择。STATUS对于此类消息,请使用或 以下内容。
Messages of STATUS through to TRACE level will be printed to stdout,
whereas NOTICE and above are printed to stderr.
This can result in messages of different log levels sometimes appearing out of
order in the output.
Furthermore, messages on stderr usually imply a problem or something that
the user should investigate, so NOTICE is generally a poor choice for purely
informational messages that don’t require follow-up.
Use STATUS or below for such messages.
STATUSthrough to的消息TRACE也可能有两个连字符和一个自动前置的空格。CMake GUI 应用程序和ccmake工具不会添加此前缀,而当前版本的cmake工具会添加此前缀。未来的 CMake 版本可能会完全删除此前缀,因此不要依赖它的存在。NOTICE对于级别及以上级别的消息,不会添加此类前缀。
Messages of STATUS through to TRACE may also have two hyphens and a space
automatically prepended.
The CMake GUI application and the ccmake tool do not prepend this prefix,
whereas the current version of the cmake tool will.
Future CMake versions may drop this prefix completely, so do not rely on it
being present.
No such prefix is prepended for messages of NOTICE level and above.
--loglevel=…CMake 3.15 还添加了使用命令行选项设置最低日志记录级别的功能
。--log-level出于一致性原因,此选项在 CMake 3.16 中被重命名为,但--loglevel出于向后兼容性的原因仍被接受。该选项指定所需的日志级别,并且仅显示该级别或更高级别的消息。如果没有--log-level给出选项,则仅STATUS记录级别或更高级别的消息。
CMake 3.15 also added the ability to set a minimum logging level with the
--loglevel=… command-line option.
This option was renamed to --log-level in CMake 3.16 for consistency reasons,
but --loglevel is still accepted for backward compatibility.
The option specifies the desired log level and only messages of that level or
higher will be shown.
When no --log-level option is given, only messages of STATUS level or
higher will be recorded.
cmake --log-level=详细 ...
cmake --log-level=VERBOSE ...
CMake 3.17 添加了使用变量指定默认日志级别的功能
CMAKE_MESSAGE_LOG_LEVEL。如果两者都存在,则它会被--log-level命令行选项覆盖。缓存变量仅供开发人员使用,项目不应尝试读取或修改它。
CMake 3.17 added the ability to specify the default log level with the
CMAKE_MESSAGE_LOG_LEVEL variable.
It is overridden by the --log-level command line option if both are present.
The cache variable is intended for developer use only, projects should not try
to read or modify it.
当项目记录大量输出时,添加一些结构可以帮助用户更好地理解每条消息与项目的哪些部分相关。一种方法是使用变量CMAKE_MESSAGE_INDENT。使用 CMake 3.16 或更高版本时,调用时此变量的内容message()将被连接并添加到日志级别及以下的消息中NOTICE。如果消息包含嵌入的换行符,则缩进内容将添加到输出的每一行之前。
When a project logs a non-trivial amount of output, adding some structure can
help the user better understand which parts of the project each message relates
to.
One way to do that is to make use of the CMAKE_MESSAGE_INDENT variable.
When using CMake 3.16 or later, the contents of this variable at the time of
the call to message() will be concatenated and prepended to the message for
log levels of NOTICE and below.
If the message contains embedded newlines, the indentation contents will be
prepended to each line of the output.
set(CMAKE_MESSAGE_INDENT aa bb) # Don't do this, see below
message("First line\nSecond line")set(CMAKE_MESSAGE_INDENT aa bb) # Don't do this, see below
message("First line\nSecond line")
aabb第一行 aabb第二行
aabbFirst line aabbSecond line
虽然上面的示例演示了该功能的工作原理,但它也存在问题。一般期望中的列表元素CMAKE_MESSAGE_INDENT
仅包含空格,通常每个元素两个空格。这不是一个要求,但偏离它可能会让用户感到烦恼。项目也不应该使用set()变量,它们应该只附加到变量上,通常通过调用list(APPEND). 这避免了对变量内容的任何假设,并始终保留现有的缩进。这对于分层项目的输出(在第 28.2 节“FetchContent”中详细讨论)或在函数调用中使用缩进时尤其重要。以下示例演示了这些准则以及如何在实践中应用它们。
While the above example demonstrates how the feature works, it has problems.
The general expectation is that the list elements in CMAKE_MESSAGE_INDENT
will only contain whitespace, typically two spaces each.
This isn’t a requirement, but deviating from it will likely be annoying for
users.
Projects should also never set() the variable, they should only append to
it, typically by calling list(APPEND).
This avoids any assumption about the contents of the variable and always
preserves the existing indenting.
This is especially important for the output of hierarchical projects
(discussed in detail in Section 28.2, “FetchContent”) or when using indenting within
function calls.
The following example demonstrates these guidelines and how they can be
applied in practice.
function(funcA)
list(APPEND CMAKE_MESSAGE_INDENT " ")
message("${CMAKE_CURRENT_FUNCTION}")
endfunction()
function(funcB)
list(APPEND CMAKE_MESSAGE_INDENT " ")
message("${CMAKE_CURRENT_FUNCTION}")
funcA()
endfunction()
function(funcC)
list(APPEND CMAKE_MESSAGE_INDENT " ")
message("${CMAKE_CURRENT_FUNCTION}")
funcB()
endfunction()
message("Top level")
funcA()
funcB()
funcC()function(funcA)
list(APPEND CMAKE_MESSAGE_INDENT " ")
message("${CMAKE_CURRENT_FUNCTION}")
endfunction()
function(funcB)
list(APPEND CMAKE_MESSAGE_INDENT " ")
message("${CMAKE_CURRENT_FUNCTION}")
funcA()
endfunction()
function(funcC)
list(APPEND CMAKE_MESSAGE_INDENT " ")
message("${CMAKE_CURRENT_FUNCTION}")
funcB()
endfunction()
message("Top level")
funcA()
funcB()
funcC()
顶层
函数A
函数B
函数A
函数C
函数B
函数ATop level
funcA
funcB
funcA
funcC
funcB
funcAfuncA()请注意和的输出缩进如何funcB()根据调用堆栈而变化。该示例的另一个特点是,由于函数引入了自己的变量范围,因此无需在返回之前将缩进从列表末尾弹出。调用者有自己独立的变量副本CMAKE_MESSAGE_INDENT,因此从其角度来看,变量的值不会因函数调用而改变。
Note how the indenting of output from both funcA() and funcB() varies
depending on the call stack.
Another feature of the example is that because functions introduce their own
variable scope, it is not necessary to pop the indent off the end of the list
before returning.
The caller has its own separate copy of the CMAKE_MESSAGE_INDENT variable, so
from its perspective, the value of the variable doesn’t change as a result of
the function call.
即使项目的最低 CMake 版本低于 3.16,也可以添加对缩进的支持。较旧的 CMake 版本将简单地忽略缩进,并且输出将保持不变。
Projects can add support for indenting even if their minimum CMake version is less than 3.16. Older CMake versions will simply ignore the indenting and output will be unchanged.
CMake 3.17 进一步扩展了对消息元数据的支持。CMAKE_MESSAGE_INDENT与可用于提供缩进的方式相同,该CMAKE_MESSAGE_CONTEXT变量可用于提供有关生成每条消息的上下文的信息。例如,这可用于记录项目名称或项目内的某些逻辑部分等内容。--log-context然后,用户可以通过在命令行中包含该选项来指示 CMake 打印每条消息的上下文信息cmake。
CMake 3.17 extended the support for message metadata even further.
In the same way that CMAKE_MESSAGE_INDENT can be used to provide indenting,
the CMAKE_MESSAGE_CONTEXT variable can be used to provide information
about the context in which each message is generated.
This can be used to record things like the project name or some logical part
within the project, for example.
Users can then instruct CMake to print context information with each message by
including the --log-context option on the cmake command line.
--log-context当给出该选项CMAKE_MESSAGE_CONTEXT且不为空时,将为调用 的每一行输出生成一个前缀message()。此前缀将是 中的项目的串联CMAKE_MESSAGE_CONTEXT,每个项目用点分隔。其结果将括在方括号中,并在前缀末尾添加一个空格。对于在STATUS级别或以下级别记录的消息,上下文位于可能由 所添加的任何前导连字符之后cmake。
When the --log-context option is given and CMAKE_MESSAGE_CONTEXT is not
empty, a prefix is generated for each line of output from a call to message().
This prefix will be the concatenation of the items in CMAKE_MESSAGE_CONTEXT,
with each item separated by a dot.
The result of that will be enclosed in square brackets and a space added to the
end of the prefix.
For messages logged at STATUS level or below, the context follows after any
leading hyphens that may be added by cmake.
一个例子最好地演示了此功能的用法:
An example best demonstrates the usage of this feature:
CMakeLists.txt:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.17)
list(APPEND CMAKE_MESSAGE_CONTEXT Coolio)
project(Coolio)
message("Adding features\nHere we go:")
add_subdirectory(networking)
add_subdirectory(graphics)
message("All done")cmake_minimum_required(VERSION 3.17)
list(APPEND CMAKE_MESSAGE_CONTEXT Coolio)
project(Coolio)
message("Adding features\nHere we go:")
add_subdirectory(networking)
add_subdirectory(graphics)
message("All done")
网络/CMakeLists.txt:
networking/CMakeLists.txt:
list(APPEND CMAKE_MESSAGE_CONTEXT net)
message("Doing something")list(APPEND CMAKE_MESSAGE_CONTEXT net)
message("Doing something")
图形/CMakeLists.txt:
graphics/CMakeLists.txt:
list(APPEND CMAKE_MESSAGE_CONTEXT graphics)
message("Doing something else")list(APPEND CMAKE_MESSAGE_CONTEXT graphics)
message("Doing something else")
在上面运行cmake --log-context将产生如下输出:
Running cmake --log-context on the above would result in output like the
following:
-- [Coolio] C编译器标识为GNU 9.3.0 -- [Coolio] CXX编译器标识为GNU 9.3.0 -- [Coolio] 检测 C 编译器 ABI 信息 -- [Coolio] 检测 C 编译器 ABI 信息 - 完成 -- [Coolio] 检查 C 编译器是否正常工作:/.../cc - 已跳过 -- [Coolio] 检测C编译特性 -- [Coolio] 检测 C 编译功能 - 完成 -- [Coolio] 检测 CXX 编译器 ABI 信息 -- [Coolio] 检测 CXX 编译器 ABI 信息 - 完成 -- [Coolio] 检查 CXX 编译器是否正常工作:/.../c++ - 已跳过 -- [Coolio] 检测 CXX 编译功能 -- [Coolio] 检测 CXX 编译功能 - 完成 [Coolio] 添加功能 [库里奥] 我们开始吧: [Coolio.net] 做某事 [Coolio.graphics] 做点别的事 [库里奥] 全部完成 -- 配置完成 -- 生成完成 -- 构建文件已写入:/...
-- [Coolio] The C compiler identification is GNU 9.3.0 -- [Coolio] The CXX compiler identification is GNU 9.3.0 -- [Coolio] Detecting C compiler ABI info -- [Coolio] Detecting C compiler ABI info - done -- [Coolio] Check for working C compiler: /.../cc - skipped -- [Coolio] Detecting C compile features -- [Coolio] Detecting C compile features - done -- [Coolio] Detecting CXX compiler ABI info -- [Coolio] Detecting CXX compiler ABI info - done -- [Coolio] Check for working CXX compiler: /.../c++ - skipped -- [Coolio] Detecting CXX compile features -- [Coolio] Detecting CXX compile features - done [Coolio] Adding features [Coolio] Here we go: [Coolio.net] Doing something [Coolio.graphics] Doing something else [Coolio] All done -- Configuring done -- Generating done -- Build files have been written to: /...
编译器功能检查由对 的调用触发project(),因此Coolio
附加到CMAKE_MESSAGE_CONTEXT该调用之前以确保检查输出包含上下文信息。输出末尾的最后几行记录了运行不同阶段的完成情况,但始终没有上下文。
Compiler feature checks are triggered by the call to project(), so Coolio
is appended to CMAKE_MESSAGE_CONTEXT before that call to ensure that the
check output includes context information.
The final few lines at the end of the output which record the completion of the
different stages of the run will always have no context.
一般期望是,当启用上下文信息的输出时,该输出将进行某种形式的进一步后处理,而不是直接显示给用户。例如,IDE 工具可能会使用它来了解输出的结构并向用户提供过滤功能。IDE 可能支持用户cmake根据自己的兴趣展开和折叠或显示和隐藏部分输出。
The general expectation is that when the output of context information is
enabled, that output is going to undergo further post-processing of some sort
rather than being shown directly to the user.
For example, an IDE tool might use this to gain understanding of the structure
of the output and offer filtering capabilities to the user.
The IDE might support the user expanding and collapsing or showing and hiding
parts of the cmake output according to their interest.
另一个用例是脚本化构建。这些可能使用 Unix 命令,如tee和awk将带注释的日志保存到文件中,但仍然显示没有上下文详细信息的输出,以允许实时监控。稍后可以搜索保存的文件以查找感兴趣的特定行。以下是在bash类 Unix 系统上的 shell 中完成此操作的一种方法(它还会去掉任何前导连字符前缀以及上下文信息):
Another use case would be scripted builds.
These might use Unix commands like tee and awk to save an annotated log to
a file, but still show the output without context details to allow it to be
monitored in real time.
The saved file can then be searched later to find specific lines of interest.
The following is one way of accomplishing this within a bash shell on a
Unix-like system (it also strips off any leading hyphen prefix along with the
context information):
.cmake . --日志上下文 |& \
开球.log | \
awk 'sub("^((--)?(\\[[^\\]]*\\]))?", "")'cmake . --log-context |& \
tee out.log | \
awk 'sub("^((-- )?(\\[[^\\]]*\\] ))?", "")'然后,可以使用以下工具从保存的日志文件中提取与网络相关的消息grep:
One could then extract just the messages associated with networking from the
saved log file using a tool like grep:
grep -E '^(-- )?\[.*Coolio\.net\] ' out.log
grep -E '^(-- )?\[.*Coolio\.net\] ' out.log
CMake 对项目可以用作上下文名称的内容设置了某些限制。有效的上下文名称(即列表中的每个项目CMAKE_MESSAGE_CONTEXT)是可以用作 CMake 变量名称的名称。在大多数情况下,这本质上意味着字母、数字和下划线。此外,CMake 认为以以cmake_下划线开头的上下文名称保留供自己使用。
CMake places certain restrictions on what a project may use as context names.
Valid context names (i.e. each item in the CMAKE_MESSAGE_CONTEXT list) are
those that could be used as the name of a CMake variable.
For the most part, this essentially means letters, numbers and underscores.
In addition, CMake considers context names that begin with cmake_ or a
leading underscore to be reserved for its own use.
message()当调用指定适当的日志级别时,消息上下文特别有效。例如,项目可以使用VERBOSE
日志级别提供更详细的信息,但在STATUS级别或更高级别仅提供相当少的输出。这将使默认输出变得整洁,但使用 of--log-level可以
VERBOSE在需要时提供额外的细节。然后,用户可以通过搜索感兴趣的消息上下文来专注于他们想要的细节。
Message contexts are particularly effective when message() calls specify
appropriate log levels.
For example, projects may provide more detailed information using a VERBOSE
log level, but only fairly minimal output at STATUS level or higher.
That would make the default output uncluttered, but using a --log-level of
VERBOSE would provide extra detail when needed.
Users could then focus in on just the details they want by searching for the
message context(s) of interest.
CMake 3.17 或更高版本提供的另一个有用功能是支持记录某种形式的检查状态的消息。语法本质上与命令的主要形式相同message(),但第一个参数的含义不同:
Another useful feature available with CMake 3.17 or later is support for
messages that record the status of some form of check.
The syntax is essentially the same as the main form of the message() command,
but the meaning of the first argument is different:
message(checkState msg1 [msg2]...)message(checkState msg1 [msg2]...)
该checkState参数预计为以下值之一:
The checkState argument is expected to be one of the following values:
CHECK_START
CHECK_START
CHECK_PASS
CHECK_PASS
CHECK_FAIL
CHECK_FAIL
开始、通过和失败消息始终以日志级别输出STATUS。成功或失败的意义取决于项目,失败并不一定意味着错误。例如,项目可能想要检查许多相关的事情,并在找到第一个成功的事情时停止。
Start, pass and fail messages are always output with a STATUS log level.
The meaning of success or failure is up to the project and failure does not
necessarily imply an error.
For example, the project may want to check for a number of related things and
stop upon the first successful one it finds.
完成检查(通过或失败)后,该message()命令将重复最近的消息CHECK_START,然后“忘记”该消息。嵌套检查的输出可以以最小的努力直观地工作。当与适当的缩进结合使用时CMAKE_MESSAGE_INDENT,输出的可读性和一致性特别好。
Upon completion of the check (pass or fail), the message() command will
repeat the message from the most recent CHECK_START and then "forget" that
message.
Output from nested checks then work intuitively with minimal effort.
When combined with appropriate indenting using CMAKE_MESSAGE_INDENT,
the readability and consistency of the output is particularly good.
# Functions just to demonstrate pass/fail behavior
function(checkSomething resultVar)
set(${resultVar} YES PARENT_SCOPE)
endfunction()
function(checkSomethingElse resultVar)
set(${resultVar} NO PARENT_SCOPE)
endfunction()
# Outer check starts here
message(CHECK_START "Checking things")
list(APPEND CMAKE_MESSAGE_INDENT " ")
# Inner check 1
message(CHECK_START "Checking support for something")
checkSomething(successVar1)
if(successVar1)
message(CHECK_PASS "supported")
else()
message(CHECK_FAIL "not supported")
endif()
# Inner check 2
message(CHECK_START "Checking support for something else")
checkSomethingElse(successVar2)
if(successVar2)
message(CHECK_PASS "supported")
else()
message(CHECK_FAIL "not supported")
endif()
# Outer check finishes here
list(POP_BACK CMAKE_MESSAGE_INDENT)
if(successVar1 OR successVar2)
message(CHECK_PASS "ok")
else()
message(CHECK_FAIL "failed")
endif()# Functions just to demonstrate pass/fail behavior
function(checkSomething resultVar)
set(${resultVar} YES PARENT_SCOPE)
endfunction()
function(checkSomethingElse resultVar)
set(${resultVar} NO PARENT_SCOPE)
endfunction()
# Outer check starts here
message(CHECK_START "Checking things")
list(APPEND CMAKE_MESSAGE_INDENT " ")
# Inner check 1
message(CHECK_START "Checking support for something")
checkSomething(successVar1)
if(successVar1)
message(CHECK_PASS "supported")
else()
message(CHECK_FAIL "not supported")
endif()
# Inner check 2
message(CHECK_START "Checking support for something else")
checkSomethingElse(successVar2)
if(successVar2)
message(CHECK_PASS "supported")
else()
message(CHECK_FAIL "not supported")
endif()
# Outer check finishes here
list(POP_BACK CMAKE_MESSAGE_INDENT)
if(successVar1 OR successVar2)
message(CHECK_PASS "ok")
else()
message(CHECK_FAIL "failed")
endif()
上面的输出将包含如下行:
Output from the above would contain lines like the following:
——检查事情 -- 检查对某事物的支持 -- 检查对某些内容的支持 - 支持 -- 检查对其他东西的支持 -- 检查对其他内容的支持 - 不支持 -- 检查东西 - 好的
-- Checking things -- Checking support for something -- Checking support for something - supported -- Checking support for something else -- Checking support for something else - not supported -- Checking things - ok
该CMakePrintHelpers模块提供了两个宏,使开发过程中打印属性和变量的值更加方便。它们并不是为了永久使用,而是更旨在帮助开发人员快速、轻松地临时记录信息,以帮助调查项目中的问题。
The CMakePrintHelpers module provides two macros which make printing the
values of properties and variables more convenient during development. They are
not intended for permanent use, but are more aimed at helping developers
quickly and easily log information temporarily to help investigate problems in
the project.
cmake_print_properties(
[TARGETS target1 [target2...]]
[SOURCES source1 [source2...]]
[DIRECTORIES dir1 [dir2...]]
[TESTS test1 [test2...]]
[CACHE_ENTRIES var1 [var2...]]
PROPERTIES property1 [property2...]
)cmake_print_properties(
[TARGETS target1 [target2...]]
[SOURCES source1 [source2...]]
[DIRECTORIES dir1 [dir2...]]
[TESTS test1 [test2...]]
[CACHE_ENTRIES var1 [var2...]]
PROPERTIES property1 [property2...]
)
该命令本质上get_property()与结合message()成一个调用。必须指定一种属性类型,并且将为列出的每个实体打印每个命名属性。当记录多个实体和/或属性的信息时,它特别方便。例如:
This command essentially combines get_property() with message() into a
single call.
Exactly one of the property types must be specified and each of the named
properties will be printed for each entity listed.
It is particularly convenient when logging information for multiple entities
and/or properties.
For example:
add_executable(MyApp main.c)
add_executable(MyAlias ALIAS MyApp)
add_library(MyLib STATIC src.cpp)
include(CMakePrintHelpers)
cmake_print_properties(TARGETS MyApp MyLib MyAlias
PROPERTIES TYPE ALIASED_TARGET
)add_executable(MyApp main.c)
add_executable(MyAlias ALIAS MyApp)
add_library(MyLib STATIC src.cpp)
include(CMakePrintHelpers)
cmake_print_properties(TARGETS MyApp MyLib MyAlias
PROPERTIES TYPE ALIASED_TARGET
)
上面的输出将是:
The output of the above would be:
目标 MyApp 的属性: MyApp.TYPE =“可执行文件” MyApp.ALIASED_TARGET = <未找到> 目标 MyLib 的属性: MyLib.TYPE =“STATIC_LIBRARY” MyLib.ALIASED_TARGET = <未找到> TARGET MyAlias 的属性: MyAlias.TYPE =“可执行文件” MyAlias.ALIASED_TARGET = "MyApp"
Properties for TARGET MyApp: MyApp.TYPE = "EXECUTABLE" MyApp.ALIASED_TARGET = <NOTFOUND> Properties for TARGET MyLib: MyLib.TYPE = "STATIC_LIBRARY" MyLib.ALIASED_TARGET = <NOTFOUND> Properties for TARGET MyAlias: MyAlias.TYPE = "EXECUTABLE" MyAlias.ALIASED_TARGET = "MyApp"
该模块还提供了类似的函数来记录一个或多个变量的值:
The module also provides a similar function for logging the value of one or more variables:
cmake_print_variables(var1 [var2...])cmake_print_variables(var1 [var2...])
这适用于所有变量,无论它们是由项目显式设置、由 CMake 自动设置还是根本未设置。
This works for all variables regardless of whether they have been explicitly set by the project, are automatically set by CMake or have not been set at all.
set(foo "My variable")
unset(bar)
include(CMakePrintHelpers)
cmake_print_variables(foo bar CMAKE_VERSION)set(foo "My variable")
unset(bar)
include(CMakePrintHelpers)
cmake_print_variables(foo bar CMAKE_VERSION)
上述的输出将类似于以下内容:
The output for the above would be something similar to the following:
foo="我的变量" ; 栏=“”;CMAKE_VERSION="3.8.2"
foo="My variable" ; bar="" ; CMAKE_VERSION="3.8.2"
为调试变量使用提供的另一种机制是
variable_watch()命令。这适用于更复杂的项目,在这些项目中可能不清楚变量如何最终获得特定值。当监视变量时,所有读取或修改它的尝试都会被记录。
Another mechanism provided for debugging variable use is the
variable_watch() command.
This is intended for more complex projects where it may not be clear how a
variable ended up with a particular value.
When a variable is watched, all attempts to read or modify it are logged.
variable_watch(myVar [command])variable_watch(myVar [command])
对于绝大多数情况,列出要监视的变量而不使用可选选项command就足够了,因为它会记录对指定变量的所有访问。为了获得更定制化的控制程度,可以给出一个命令,每次读取或修改变量时都会执行该命令。该命令应该是 CMake 函数或宏的名称,它将接收以下参数:
For the vast majority of cases, listing the variable to be watched
without the optional command is sufficient, as it logs all accesses to the
nominated variable.
For a more customized degree of control, a command can be given which will be
executed every time the variable is read or modified.
The command is expected to be the name of a CMake function or macro, which will
receive the following arguments:
在实践中,指定命令variable_watch()是非常罕见的。默认消息通常足以帮助诊断通常使用的情况
variable_watch()。默认消息在调用堆栈中还包含比传递给自定义观察程序的最后一个参数更多的详细信息command。
In practice, specifying a command with variable_watch() would be very
uncommon.
The default message is usually enough to help diagnose the situations where
variable_watch() is typically used.
The default message also contains more detail in the call stack than is
available in the last argument passed to a custom watcher command.
生成器表达式很快就会变得复杂,并且很难确认其正确性。由于它们仅在生成时进行评估,因此它们的结果在配置阶段不可用,因此无法使用命令打印
message()。
Generator expressions can quickly become complicated and it can be difficult to
confirm their correctness.
Because they are only evaluated at generation time, their results are not
available during the configure stage and therefore cannot be printed using the
message() command.
调试生成器表达式的值的一种方法是使用命令
,该命令在第 19.3 节“直接读写文件”file(GENERATE)中介绍
。生成器表达式可以写入临时文件并在 CMake 完成执行时进行检查。例如:
One way to debug the value of a generator expression is to use the
file(GENERATE) command, which is covered in
Section 19.3, “Reading And Writing Files Directly”.
The generator expression can be written to a temporary file and inspected when
CMake finishes execution.
For example:
add_executable(someTarget ...)
target_include_directories(someTarget ...)
set(incDirs
$<TARGET_PROPERTY:someTarget,INCLUDE_DIRECTORIES>
)
set(genex "-I$<JOIN:${incDirs}, -I>")
file(GENERATE OUTPUT genex.txt CONTENT "${genex}\n")add_executable(someTarget ...)
target_include_directories(someTarget ...)
set(incDirs
$<TARGET_PROPERTY:someTarget,INCLUDE_DIRECTORIES>
)
set(genex "-I$<JOIN:${incDirs}, -I>")
file(GENERATE OUTPUT genex.txt CONTENT "${genex}\n")
另一种方法是创建一个临时自定义构建目标,其命令打印生成器表达式的值(请参见第 18.1 节“自定义目标”)。构建该目标然后打印表达式的结果。
Another approach is to create a temporary custom build target whose command prints the value of the generator expression (see Section 18.1, “Custom Targets”). Building that target then prints the result of the expression.
add_custom_target(printGenex
COMMENT "Result of generator expression:"
COMMAND ${CMAKE_COMMAND} -E echo "${genex}"
VERBATIM
)add_custom_target(printGenex
COMMENT "Result of generator expression:"
COMMAND ${CMAKE_COMMAND} -E echo "${genex}"
VERBATIM
)
构建该目标和一些代表性输出可能如下所示:
Building that target and some representative output might look like this:
cmake --build 。--目标打印Genex [1/1] 生成器表达式的结果: -I/一些/路径 -I/一些/其他/路径
cmake --build . --target printGenex [1/1] Result of generator expression: -I/some/path -I/some/other/path
此技术对于特定于配置的生成器表达式以及使用 Xcode、Visual Studio 和 Ninja Multi-Config 等多配置生成器时特别有用:
This technique is especially useful for configuration-specific generator expressions and when using multi-config generators like Xcode, Visual Studio and Ninja Multi-Config:
set(genex "$<IF:$<CONFIG:Debug>,is debug,not debug>")
add_custom_target(printGenex
COMMENT "Result of generator expression:"
COMMAND ${CMAKE_COMMAND} -E echo "${genex}"
VERBATIM
)set(genex "$<IF:$<CONFIG:Debug>,is debug,not debug>")
add_custom_target(printGenex
COMMENT "Result of generator expression:"
COMMAND ${CMAKE_COMMAND} -E echo "${genex}"
VERBATIM
)
对于多配置生成器,可以使用 build 命令指定配置:
For multi-configuration generators, the configuration can be specified with the build command:
cmake --build 。--target printGenex --config 发布 [1/1] 生成器表达式的结果: 不调试
cmake --build . --target printGenex --config Release [1/1] Result of generator expression: not debug
cmake --build 。--目标 printGenex --config 调试 [1/1] 生成器表达式的结果: 正在调试
cmake --build . --target printGenex --config Debug [1/1] Result of generator expression: is debug
CMake 3.18 添加了分析 CMake 自己的项目处理的功能。对于配置阶段需要很长时间的大型复杂项目,这可以提供有关时间花费在哪里的宝贵见解。启用分析后,每个 CMake 命令调用都会记录在分析输出中。
CMake 3.18 added the ability to profile CMake’s own processing of a project. For large, complicated projects where the configure stage takes a long time, this can provide valuable insights into where time is being spent. When profiling is enabled, every CMake command invocation is recorded in the profiling output.
需要提供以下两个命令行选项才能启用分析:
Both of the following command line options need to be provided to enable profiling:
--profiling-output=fileName
--profiling-output=fileName
fileName.
fileName.
--profiling-format=fmt
--profiling-format=fmt
fmt是google-trace,但未来的 CMake 版本可能会扩展此值以包括其他格式。
fmt is google-trace, but future CMake versions
may expand this to include other formats.
对于google-trace格式,输出文件可以直接加载到 Chrome Web 浏览器(导航至 URL about:tracing)或某些 IDE(例如 Qt Creator)中。使用.json输出文件名的扩展名可以更轻松地查找并加载到理解该google-trace格式的工具中。
For the google-trace format, the output file can be loaded directly into a
Chrome web browser (navigate to the URL about:tracing) or some IDEs (e.g.
Qt Creator).
Using a .json extension for the output file name may make it easier to find
and load into tools that understand the google-trace format.
分析结果通常显示类似try_compile()和 的
调用execute_process()消耗了大部分执行时间。不要专门关注这两个调用,而是检查导致这些命令的调用堆栈。通过避免调用堆栈中更高层的不必要或过于悲观的逻辑,可能有机会减少调用这两个命令的频率。
The profiling results often show calls like try_compile() and
execute_process() as consuming the majority of the execution time.
Rather than focusing on those two calls specifically, inspect the call stacks
that led up to those commands.
There may be opportunities for reducing how often these two commands are called
by avoiding unnecessary or overly pessimistic logic higher in the call stack.
许多项目的一个常见问题是它们在配置步骤期间记录过多的输出。这往往会训练用户忽略输出,这反过来又意味着很容易错过重要的消息和警告。当输出相当少并且确实出现警告时,用户往往会注意并调查原因。因此,目标是最大限度地减少日志级别的输出量STATUS,为 或更低的日志级别保存更详细的输出VERBOSE。如果支持 3.15 之前的 CMake 版本(其中以下日志级别STATUS
不可用),请考虑将详细日志记录置于项目特定的缓存选项后面,该选项默认情况下应处于关闭状态。
A common problem with many projects is that they log an excessive amount of
output during the configure step.
This tends to train users to ignore the output, which in turn means that
important messages and warnings are easily missed.
When the output is fairly minimal and a warning does occur, users tend to
take note and investigate the cause.
Therefore, aim to minimize the amount of output at the STATUS log level,
saving more detailed output for a log level of VERBOSE or lower.
If supporting CMake versions older than 3.15 where log levels below STATUS
are not available, consider putting detailed logging behind a project-specific
cache option, which should be off by default.
对于打算保留为构建一部分的日志消息,目标是始终将日志级别指定为命令的第一个参数message()。如果消息具有一般信息性质,则最好使用STATUS
关键字而不是根本不使用关键字,以便消息输出不会在构建日志中出现乱序。为了方便起见,临时调试消息经常省略指定日志级别,但如果它们可能在任意时间内保留在项目中,那么最好也指定日志级别。
For log messages intended to remain as part of the build, aim to always specify
a log level as the first argument to the message() command.
If the message is of a general informational nature, prefer to use STATUS
rather than no keyword at all so that message output does not appear out of
order in the build log.
Temporary debugging messages frequently omit specifying a log level for
convenience, but if they are likely to remain part of the project for any
length of time, it is better that they too specify a log level.
对于重要的项目,请考虑添加消息上下文信息,以允许用户过滤日志输出并仅关注他们感兴趣的那些消息。切勿丢弃变量的现有内容,始终在启动新消息上下文时CMAKE_MESSAGE_CONTEXT使用。list(APPEND)如果消息上下文应在当前变量范围结束之前结束,请使用list(POP_BACK). 除了可以使用此追加/弹出模式之外,不要对变量包含的内容做出任何假设。project()考虑在顶级文件中的第一次调用之前将项目名称作为消息上下文附加CMakeLists.txt,以便编译器功能检查也具有消息上下文。
For non-trivial projects, consider adding message context information to allow
users to filter log output and focus on just those messages that are of
interest to them.
Never discard existing contents of the CMAKE_MESSAGE_CONTEXT variable, always
use list(APPEND) when starting a new message context.
If the message context should end before the end of the current variable scope,
use list(POP_BACK).
Make no assumptions about what the variable contains other than that this
append / pop back pattern can be used.
Consider appending the project name as a message context immediately before the
first project() call in the top level CMakeLists.txt file so that compiler
feature checks also have a message context.
以类似的方式,还可以考虑使用CMAKE_MESSAGE_INDENT变量为消息输出提供某种逻辑结构。最好添加两个空格以进行缩进。虽然允许其他缩进,但遵循此约定将使输出更加一致,尤其是在使用外部依赖项的分层项目中。用于list(APPEND)添加现有缩进,切勿替换或丢弃CMAKE_MESSAGE_INDENT变量的现有内容。如果需要,list(POP_BACK)可用于在当前变量作用域结束之前再次减少缩进。
In a similar manner, also consider using the CMAKE_MESSAGE_INDENT variable to
provide some logical structure to the message output.
Prefer to append two spaces for an indent.
While other indents are permitted, following this convention will make the
output more consistent, especially in hierarchical projects that make use of
external dependencies.
Use list(APPEND) to add to the existing indent, never replace or discard the
existing contents of the CMAKE_MESSAGE_INDENT variable.
If required, list(POP_BACK) can be used to reduce the indent again before the
end of the current variable scope.
无论支持的最低 CMake 版本如何,项目都可以填充CMAKE_MESSAGE_CONTEXT和变量。CMAKE_MESSAGE_INDENT当使用不知道这些变量的早期 CMake 版本时,它将简单地忽略它们并且输出将不受影响。因此,即使项目需要支持早期的 CMake 版本,也请考虑使用这些功能。请注意,该list(POP_BACK)命令需要 CMake 3.15 或更高版本,因此如果项目需要支持早于该版本的版本,则必须使用替代命令来实现所需的相同效果。但在大多数情况下,新的消息上下文或缩进级别将应用到当前变量范围的末尾,在这种情况下,不需要从列表变量的末尾弹出最后一个值。
Both the CMAKE_MESSAGE_CONTEXT and CMAKE_MESSAGE_INDENT variables can be
populated by the project regardless of the minimum supported CMake version.
When an earlier CMake version that doesn’t know about these variables is used,
it will simply ignore them and the output will be unaffected.
Therefore, consider using these features even if the project needs to support
earlier CMake versions.
Note that the list(POP_BACK) command requires CMake 3.15 or later, so if the
project needs to support versions earlier that that, it must use alternative
commands to achieve the same effect where that is needed.
In most cases though, a new message context or indentation level will apply
through to the end of the current variable scope, in which case popping the
last value from the end of the list variable won’t be necessary.
考虑使用命令的CHECK_START,CHECK_PASS和CHECK_FAIL形式
message()来记录检查的详细信息。这减少了消息的重复并提高了可读性。当与变量提供的缩进支持结合使用时,它特别有效CMAKE_MESSAGE_INDENT。
Consider using the CHECK_START, CHECK_PASS and CHECK_FAIL form of the
message() command to record details of checks.
This reduces duplication of messages and provides improved readability.
It is especially effective when used in conjunction with the indenting support
provided by the CMAKE_MESSAGE_INDENT variable.
如果项目的配置阶段需要很长时间才能完成,请考虑使用cmake和--profiling-output选项运行--profiling-format以调查时间花在哪里。这些选项在 CMake 3.18 或更高版本中提供,可以生成命令级分析信息,可以使用 Chrome Web 浏览器等工具或 Qt Creator 等 IDE 来查看这些信息。
If the configure stage of a project takes a long time to complete, consider
running cmake with the --profiling-output and --profiling-format options
to investigate where the time is being spent.
Available with CMake 3.18 or later, these options enable the generation of
command-level profiling information which can be viewed with tools like the
Chrome web browser or IDEs like Qt Creator.
第 24.6 节“调试 find_…() 调用”还讨论了 CMake 3.18 中添加的进一步调试功能。这些功能与查找文件、包和其他内容相关,第 24 章“查找内容”中有详细介绍。
Section 24.6, “Debugging find_…() Calls” also discusses further debugging features added in CMake 3.18. Those features relate to finding files, packages and other things, which is covered in detail in Chapter 24, Finding Things.
前面的章节逐步介绍了CMake最基本的方面。介绍了核心语言功能、关键概念和重要构建块,为更深入探索 CMake 的功能奠定了坚实的基础。
In the preceding chapters, the most fundamental aspects of CMake were progressively introduced. Core language features, key concepts and important building blocks were presented, providing a solid foundation for a deeper exploration of CMake’s functionality.
在本书的这一部分中,构建产品成为焦点。章节涵盖工具链和构建配置、不同类型的目标、执行自定义任务和处理特定于平台的功能。充分理解这些领域可能是一个脆弱、复杂的项目和一个强大、易于维护的项目之间的区别。
In this part of the book, the build products become the focus. Chapters cover the toolchain and build configuration, different types of targets, carrying out custom tasks and handling platform-specific features. Understanding these areas well can be the difference between a fragile, complex project and a robust, easy to maintain one.
本章和下一章涵盖两个密切相关的主题。构建类型(在某些 IDE 工具中也称为构建配置或构建方案)是一种高级控制,它选择不同的编译器和链接器行为集。构建类型的操作是本章的主题,而下一章将介绍控制编译器和链接器选项的更具体细节。这些章节涵盖了每个 CMake 开发人员通常用于除最琐碎项目之外的所有项目的材料。
This chapter and the next cover two closely related topics. The build type (also known as the build configuration or build scheme in some IDE tools) is a high level control which selects different sets of compiler and linker behavior. Manipulation of the build type is the subject of this chapter, while the next chapter presents more specific details of controlling compiler and linker options. Together, these chapters cover material every CMake developer will typically use for all but the most trivial projects.
构建类型有可能以某种方式影响构建的几乎所有内容。虽然它主要对编译器和链接器行为有直接影响,但它也会对项目使用的目录结构产生影响。这反过来又会影响开发人员如何设置自己的本地开发环境,因此构建类型的影响可能相当深远。
The build type has the potential to affect almost everything about the build in one way or another. While it primarily has a direct effect on the compiler and linker behavior, it also has an effect on the directory structure used for a project. This can in turn influence how a developer sets up their own local development environment, so the effects of the build type can be quite far reaching.
开发人员通常认为构建是两种安排之一:调试或发布。对于调试版本,编译器标志用于记录调试器可用于将机器指令与源代码关联的信息。在此类构建中经常禁用优化,以便在逐步执行程序时从机器指令到源代码位置的映射是直接且易于遵循的。另一方面,发布版本通常启用完全优化并且不生成调试信息。
Developers commonly think of builds as being one of two arrangements: debug or release. For a debug build, compiler flags are used to enable the recording of information that debuggers can use to associate machine instructions with the source code. Optimizations are frequently disabled in such builds so that the mapping from machine instruction to source code location is direct and easy to follow when stepping through program execution. A release build, on the other hand, generally has full optimizations enabled and no debug information generated.
这些是 CMake 所指的构建类型的示例。虽然项目能够定义它们想要的任何构建类型,但 CMake 提供的默认构建类型通常足以满足大多数项目的需求:
These are examples of what CMake refers to as the build type. While projects are able to define whatever build types they want, the default build types provided by CMake are usually sufficient for most projects:
Release构建的性能,但仍然允许一定程度的调试。通常会应用大多数速度优化,但也会启用大多数调试功能。因此,当构建的性能Debug即使对于调试会话而言也是不可接受的时,此构建类型非常有用。请注意,默认设置RelWithDebInfo将禁用断言。
Release build, but still allow some level of
debugging. Most optimizations for speed are typically applied, but most debug
functionality is also enabled. This build type is therefore most useful when
the performance of a Debug build is not acceptable even for a debugging
session. Note that the default settings for RelWithDebInfo will disable
assertions.
每种构建类型都会产生一组不同的编译器和链接器标志。它还可能会更改其他行为,例如更改编译哪些源文件或链接到哪些库。这些细节将在接下来的几节中介绍,但在开始这些讨论之前,有必要了解如何选择构建类型以及如何避免一些常见问题。
Each build type results in a different set of compiler and linker flags. It may also change other behaviors, such as altering which source files get compiled or what libraries to link to. These details are covered in the next few sections, but before launching into those discussions, it is essential to understand how to select the build type and how to avoid some common problems.
回到第 2.3 节“生成项目文件”,介绍了不同类型的项目生成器。有些(例如 Makefiles 和 Ninja)仅支持每个构建目录单一构建类型。对于这些生成器,必须通过设置CMAKE_BUILD_TYPE缓存变量来选择构建类型。例如,要使用 Ninja 配置并构建一个项目,可以使用如下命令:
Back in Section 2.3, “Generating Project Files”, the different types of project generators
were introduced. Some, like Makefiles and Ninja, support only a single build
type per build directory. For these generators, the build type has to be chosen
by setting the CMAKE_BUILD_TYPE cache variable. For example, to configure
and then build a project with Ninja, one might use commands like this:
cmake -G Ninja -DCMAKE_BUILD_TYPE:STRING=调试../源 cmake --build 。
cmake -G Ninja -DCMAKE_BUILD_TYPE:STRING=Debug ../source cmake --build .
缓存CMAKE_BUILD_TYPE变量也可以在 CMake GUI 应用程序中更改,而不是从命令行更改,但最终效果是相同的。然而,另一种策略是为每种构建类型设置单独的构建目录,所有这些仍然使用相同的源,而不是在不同的构建类型之间切换。这样的目录结构可能看起来像这样:
The CMAKE_BUILD_TYPE cache variable can also be changed in the CMake GUI
application instead of from the command line, but the end effect is the same.
Rather than switching between different build types, however, an alternative
strategy is to set up separate build directories for each build type, all still
using the same sources. Such a directory structure might look something like
this:
如果经常在构建类型之间切换,这种安排可以避免仅仅因为编译器标志发生变化而不断地重新编译相同的源。它还允许单个配置生成器有效地像多配置生成器一样工作,像 Qt Creator 这样的 IDE 环境支持在构建目录之间切换,就像 Xcode 或 Visual Studio 允许在构建方案或配置之间切换一样轻松。
If frequently switching between build types, this arrangement avoids having to constantly recompile the same sources just because compiler flags change. It also allows a single configuration generator to effectively act like a multi configuration generator, with IDE environments like Qt Creator supporting switching between build directories just as easily as Xcode or Visual Studio allow switching between build schemes or configurations.
一些生成器,特别是 Xcode 和 Visual Studio,支持单个构建目录中的多个配置。从 CMake 3.17 开始,Ninja Multi-Config 生成器也可用。这些多配置生成器忽略CMAKE_BUILD_TYPE缓存变量,而是要求开发人员在 IDE 中选择构建类型或在构建时使用命令行选项。配置和构建此类项目通常如下所示:
Some generators, notably Xcode and Visual Studio, support multiple
configurations in a single build directory.
From CMake 3.17, the Ninja Multi-Config generator is also available.
These multi-config generators ignore the CMAKE_BUILD_TYPE cache variable and
instead require the developer to choose the build type within the IDE or with
a command line option at build time.
Configuring and building such projects would typically look something like this:
cmake -G Xcode ../源 cmake --build 。--配置调试
cmake -G Xcode ../source cmake --build . --config Debug
在 Xcode IDE 中构建时,构建类型由构建方案控制,而在 Visual Studio IDE 中,当前解决方案配置控制构建类型。两种环境都为不同的构建类型保留单独的目录,因此构建之间的切换不会导致不断的重建。实际上,与上面针对单个配置生成器描述的多个构建目录安排执行相同的操作,只是 IDE 代表开发人员处理目录结构。
When building within the Xcode IDE, the build type is controlled by the build scheme, while within the Visual Studio IDE, the current solution configuration controls the build type. Both environments keep separate directories for the different build types, so switching between builds doesn’t cause constant rebuilds. In effect, the same thing is being done as the multiple build directory arrangement described above for single configuration generators, it’s just that the IDE is handling the directory structure on the developer’s behalf.
对于命令行构建,Ninja 多配置生成器比其他多配置生成器具有更大的灵活性。当构建命令行上未指定配置时,缓存CMAKE_DEFAULT_BUILD_TYPE变量可用于更改要使用的默认配置。Xcode 和 Visual Studio 生成器有自己的固定逻辑来确定此场景中的默认配置。Ninja 多配置生成器还支持高级功能,允许自定义命令作为一种配置执行,但其他目标可以使用一种或多种其他配置构建。大多数项目通常不需要或受益于这些更高级的功能,但 Ninja 多重配置生成器的 CMake 文档提供了基本细节和示例。
For command-line builds, the Ninja Multi-Config generator has a little more
flexibility compared to the other multi-config generators.
The CMAKE_DEFAULT_BUILD_TYPE cache variable can be used to change the
default configuration to use when no configuration is specified on the build
command line.
The Xcode and Visual Studio generators have their own fixed logic for
determining the default configuration in this scenario.
The Ninja Multi-Config generator also supports advanced features that allow
custom commands to execute as one configuration, but other targets to be built
with one or more other configurations.
Most projects would not typically need or benefit from these more advanced
features, but the CMake documentation for the Ninja Multi-Config generator
provides the essential details, with examples.
请注意,对于单配置生成器,构建类型是在
配置时指定的,而对于多配置生成器,构建类型是在构建时指定的。这种区别至关重要,因为这意味着 CMake 处理项目CMakeLists.txt
文件时并不总是知道构建类型。考虑以下 CMake 代码,不幸的是,它相当常见,但演示了错误的模式:
Note how for single configuration generators, the build type is specified at
configure time, whereas for multi configuration generators, the build type is
specified at build time. This distinction is critical, as it means the build
type is not always known when CMake is processing a project’s CMakeLists.txt
file. Consider the following piece of CMake code, which unfortunately is
rather common, but demonstrates an incorrect pattern:
# WARNING: Do not do this!
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
# Do something only for debug builds
endif()# WARNING: Do not do this!
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
# Do something only for debug builds
endif()
以上对于基于 Makefile 的生成器和 Ninja 来说可以正常工作,但不适用于 Xcode、Visual Studio 或 Ninja Multi-Config。在实践中,几乎任何基于CMAKE_BUILD_TYPE项目内的逻辑都是有问题的,除非它受到检查以确认正在使用单个配置生成器的保护。对于多配置生成器,此变量可能为空,但即使不是,其值也应被视为不可靠,因为构建会忽略它。项目不应CMAKE_BUILD_TYPE在CMakeLists.txt文件中引用,而应使用其他更强大的替代技术,例如基于$<CONFIG:…>.
The above would work fine for Makefile-based generators and Ninja, but not for
Xcode, Visual Studio or Ninja Multi-Config.
In practice, just about any logic based on CMAKE_BUILD_TYPE within a project
is questionable unless it is protected by a check to confirm a single
configuration generator is being used.
For multi configuration generators, this variable is likely to be empty, but
even if it isn’t, its value should be considered unreliable because the build
will ignore it.
Rather than referring to CMAKE_BUILD_TYPE in the CMakeLists.txt file,
projects should instead use other more robust alternative techniques, such as
generator expressions based on $<CONFIG:…>.
当脚本构建时,一个常见的缺陷是假设使用了特定的生成器,或者没有正确考虑单配置生成器和多配置生成器之间的差异。理想情况下,开发人员应该能够在一处更改生成器,并且脚本的其余部分仍应正常运行。方便的是,单配置生成器将忽略任何构建时规范,而多配置生成器将忽略
CMAKE_BUILD_TYPE变量,因此通过指定两者,脚本可以解释这两种情况。例如:
When scripting builds, a common deficiency is to assume a particular generator
is used or to not properly account for differences between single and multi
configuration generators. Developers should ideally be able to change the
generator in one place and the rest of the script should still function
correctly. Conveniently, single configuration generators will ignore any
build-time specification and multi configuration generators will ignore the
CMAKE_BUILD_TYPE variable, so by specifying both, a script can account for
both cases. For example:
mkdir 构建 光盘构建 cmake -G Ninja -DCMAKE_BUILD_TYPE =发布../源 cmake --build 。--config 发布
mkdir build cd build cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ../source cmake --build . --config Release
通过上面的示例,开发人员可以简单地更改为-G参数指定的生成器名称,而脚本的其余部分将保持不变。
With the above example, a developer could simply change the generator name
given to the -G parameter and the rest of the script would work unchanged.
不明确设置CMAKE_BUILD_TYPE单个配置生成器也很常见,但通常不是开发人员想要的。单个配置生成器的一个独特行为是,如果
CMAKE_BUILD_TYPE未设置,构建类型通常为空。这有时会导致一些开发人员误解,认为空构建类型相当于 Debug,但事实并非如此。空构建类型是它自己独特的、无名的构建类型。在这种情况下,不使用特定于配置的编译器或链接器标志,这通常会导致使用最少的标志调用编译器和链接器。然后,该行为由编译器和链接器自己的默认值决定。虽然这通常可能与“调试”构建类型的行为类似,但绝不能保证这一点。
Not explicitly setting the CMAKE_BUILD_TYPE for single configuration
generators is also common, but usually not what the developer intended.
A behavior unique to single configuration generators is that if
CMAKE_BUILD_TYPE is not set, the build type will usually be empty.
This occasionally leads to a misunderstanding by some developers that an empty
build type is equivalent to Debug, but this is not the case.
An empty build type is its own unique, nameless build type.
In such cases, no configuration-specific compiler or linker flags are used,
which often results in invoking the compiler and linker with minimal flags.
The behavior is then determined by the compiler’s and linker’s own defaults.
While this may often be similar to the Debug build type’s behavior, it is by
no means guaranteed.
将 Visual Studio 编译器与单个配置生成器一起使用是一种特殊情况。对于该工具链,有不同的运行时库用于调试和非调试构建。空的构建类型会导致不清楚应该使用哪个运行时。为了避免这种歧义,此组合的构建类型将默认为“调试”。
Using the Visual Studio compilers with a single configuration generator is somewhat of a special case. For that toolchain, there are different runtime libraries for debug and non-debug builds. An empty build type would make it unclear which runtime should be used. To avoid this ambiguity, the build type will default to Debug for this combination.
有时,项目可能希望将构建类型集限制为默认值的子集,或者可能希望使用一组特殊的编译器和链接器标志添加其他自定义构建类型。后者的一个很好的例子是添加用于分析或代码覆盖的构建类型,这两者都需要特定的编译器和链接器设置。
Sometimes a project may want to limit the set of build types to a subset of the defaults, or it may want to add other custom build types with a special set of compiler and linker flags. A good example of the latter is adding a build type for profiling or code coverage, both of which require specific compiler and linker settings.
开发人员可以在两个主要位置看到构建类型集。当使用 Xcode 和 Visual Studio 等多配置生成器的 IDE 环境时,IDE 会提供一个下拉列表或类似的列表,开发人员可以从中选择他们想要构建的配置。对于像 Makefiles 或 Ninja 这样的单一配置生成器,直接为缓存变量输入构建类型CMAKE_BUILD_TYPE,但可以使 CMake GUI 应用程序显示有效选择的组合框,而不是简单的文本编辑字段。这两种情况背后的机制不同,因此必须分别处理。
There are two main places where a developer may see the set of build types.
When using IDE environments for multi configuration generators like Xcode and
Visual Studio, the IDE provides a drop-down list or similar from which the
developer selects the configuration they wish to build.
For single configuration generators like Makefiles or Ninja, the build type is
entered directly for the CMAKE_BUILD_TYPE cache variable, but the CMake GUI
application can be made to present a combo box of valid choices instead of a
simple text edit field.
The mechanisms behind these two cases are different, so they must be handled
separately.
多配置生成器已知的构建类型集由CMAKE_CONFIGURATION_TYPES缓存变量控制,或更准确地说,由处理顶级CMakeLists.txt
文件结束时该变量的值控制。project()如果尚未定义,第一个遇到的命令将使用默认列表填充缓存变量,但项目可能会在此之后修改同名的非缓存变量(修改缓存变量是不安全的,因为它可能会丢弃由开发商)。可以通过将自定义构建类型添加到该列表来定义它们CMAKE_CONFIGURATION_TYPES,并且可以从该列表中删除不需要的构建类型。
The set of build types known to multi configuration generators is controlled by
the CMAKE_CONFIGURATION_TYPES cache variable, or more accurately, by the
value of this variable at the end of processing the top level CMakeLists.txt
file. The first encountered project() command populates the cache variable
with a default list if it has not already been defined, but projects may modify
the non-cache variable of the same name after that point (modifying the cache
variable is unsafe since it may discard changes made by the developer). Custom
build types can be defined by adding them to CMAKE_CONFIGURATION_TYPES and
unwanted build types can be removed from that list.
但是,需要注意避免CMAKE_CONFIGURATION_TYPES
在尚未定义的情况下进行设置。在 CMake 3.9 之前,确定是否使用多配置生成器的一种非常常见的方法是检查是否CMAKE_CONFIGURATION_TYPES非空。甚至 CMake 本身的一部分在 3.11 之前也这样做了。CMAKE_CONFIGURATION_TYPES虽然这种方法通常是准确的,但即使使用单个配置生成器,单方面设置项目的情况也并不罕见。这可能会导致对所使用的发电机类型做出错误的决定。为了解决这个问题,CMake 3.9 添加了一个新的
GENERATOR_IS_MULTI_CONFIG全局属性,该属性在使用多配置生成器时设置为 true,提供了一种获取该信息的明确方法,而不是依赖于从
CMAKE_CONFIGURATION_TYPES. 即便如此,检查CMAKE_CONFIGURATION_TYPES仍然是一种普遍的模式,项目应该继续只修改它(如果它存在),而不是自己创建它。还应该注意的是,在 CMake 3.11 之前,添加自定义构建类型在CMAKE_CONFIGURATION_TYPES技术上并不安全。CMake 的某些部分仅考虑默认构建类型,但即便如此,项目仍然可以使用早期 CMake 版本有效地定义自定义构建类型,具体取决于它们的使用方式。也就是说,为了更好的稳健性,如果要定义自定义构建类型,仍然建议至少使用 CMake 3.11。
Care needs to be taken, however, to avoid setting CMAKE_CONFIGURATION_TYPES
if it is not already defined. Prior to CMake 3.9, a very common approach for
determining whether a multi configuration generator was being used was to check
if CMAKE_CONFIGURATION_TYPES was non-empty. Even parts of CMake itself did
this prior to 3.11. While this method is usually accurate, it is not unusual to
see projects unilaterally set CMAKE_CONFIGURATION_TYPES even if using a
single configuration generator. This can lead to wrong decisions being made
regarding the type of generator in use. To address this, CMake 3.9 added a new
GENERATOR_IS_MULTI_CONFIG global property which is set to true when a
multi configuration generator is being used, providing a definitive way to
obtain that information instead of relying on inferring it from
CMAKE_CONFIGURATION_TYPES. Even so, checking CMAKE_CONFIGURATION_TYPES is
still such a prevalent pattern that projects should continue to only modify it
if it exists and never create it themselves. It should also be noted that prior
to CMake 3.11, adding custom build types to CMAKE_CONFIGURATION_TYPES was not
technically safe. Certain parts of CMake only accounted for the default build
types, but even so, projects may still be able to usefully define custom build
types with earlier CMake versions, depending on how they are going to be used.
That said, for better robustness, it is still recommended to use at least CMake
3.11 if custom build types are going to be defined.
这个问题的另一个方面是,开发人员可能会将自己的类型添加到
CMAKE_CONFIGURATION_TYPES缓存变量中和/或删除他们不感兴趣的类型。因此,项目不应对已定义或未定义的配置类型做出任何假设。
Another aspect of this issue is that developers may add their own types to the
CMAKE_CONFIGURATION_TYPES cache variable and/or remove those they are not
interested in.
Projects should therefore not make any assumptions about what configuration
types are or are not defined.
考虑到上述几点,以下模式显示了项目为多配置生成器添加自己的自定义构建类型的首选方式:
Taking the above points into account, the following pattern shows the preferred way for projects to add their own custom build types for multi configuration generators:
cmake_minimum_required(3.11)
project(Foo)
get_property(isMultiConfig GLOBAL
PROPERTY GENERATOR_IS_MULTI_CONFIG
)
if(isMultiConfig)
if(NOT "Profile" IN_LIST CMAKE_CONFIGURATION_TYPES)
list(APPEND CMAKE_CONFIGURATION_TYPES Profile)
endif()
endif()
# Set relevant Profile-specific flag variables as needed...cmake_minimum_required(3.11)
project(Foo)
get_property(isMultiConfig GLOBAL
PROPERTY GENERATOR_IS_MULTI_CONFIG
)
if(isMultiConfig)
if(NOT "Profile" IN_LIST CMAKE_CONFIGURATION_TYPES)
list(APPEND CMAKE_CONFIGURATION_TYPES Profile)
endif()
endif()
# Set relevant Profile-specific flag variables as needed...
对于单个配置生成器,只有一种构建类型,并且由CMAKE_BUILD_TYPE缓存变量指定,该变量是一个字符串。在 CMake GUI 中,这通常显示为文本编辑字段,因此开发人员可以对其进行编辑以包含他们想要的任何任意内容。然而,正如第 9.6 节“缓存变量属性”中所讨论的,缓存变量可以定义其STRINGS属性来保存一组有效值。然后,CMake GUI 应用程序将该变量呈现为包含有效值的组合框,而不是文本编辑字段。
For single configuration generators, there is only one build type and it is
specified by the CMAKE_BUILD_TYPE cache variable, which is a string.
In the CMake GUI, this is normally presented as a text edit field, so the
developer can edit it to contain whatever arbitrary content they wish. As
discussed back in Section 9.6, “Cache Variable Properties”, however, cache variables can
have their STRINGS property defined to hold a set of valid values. The CMake
GUI application will then present that variable as a combo box containing the
valid values instead of as a text edit field.
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
STRINGS Debug Release Profile
)set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
STRINGS Debug Release Profile
)
属性只能从项目CMakeLists.txt文件内更改,因此他们可以安全地设置STRINGS属性,而不必担心保留任何开发人员更改。但请注意,设置STRINGS
缓存变量的属性并不能保证缓存变量将保存已定义的值之一,它仅控制变量在 CMake GUI 应用程序中的呈现方式。开发人员仍然可以在命令行中设置CMAKE_BUILD_TYPE为任何值cmake或手动编辑CMakeCache.txt文件。为了严格要求变量具有定义的值之一,项目必须明确地自行执行该测试。
Properties can only be changed from within the project’s CMakeLists.txt files,
so they can safely set the STRINGS property without having to worry about
preserving any developer changes. Note, however, that setting the STRINGS
property of a cache variable does not guarantee that the cache variable will
hold one of the defined values, it only controls how the variable is presented
in the CMake GUI application. Developers can still set CMAKE_BUILD_TYPE to
any value at the cmake command line or edit the CMakeCache.txt file
manually. In order to rigorously require the variable to have one of the
defined values, a project must explicitly perform that test itself.
set(allowedBuildTypes Debug Release Profile)
# WARNING: This logic is not sufficient
if(NOT CMAKE_BUILD_TYPE IN_LIST allowedBuildTypes)
message(FATAL_ERROR
"${CMAKE_BUILD_TYPE} is not a known build type"
)
endif()set(allowedBuildTypes Debug Release Profile)
# WARNING: This logic is not sufficient
if(NOT CMAKE_BUILD_TYPE IN_LIST allowedBuildTypes)
message(FATAL_ERROR
"${CMAKE_BUILD_TYPE} is not a known build type"
)
endif()
的默认值CMAKE_BUILD_TYPE是一个空字符串,因此除非开发人员明确设置它,否则上述内容对于单配置生成器和多配置生成器都会导致致命错误。这是不希望的,特别是对于甚至不使用CMAKE_BUILD_TYPE变量值的多配置生成器。CMAKE_BUILD_TYPE如果尚未设置,可以通过让项目提供默认值来处理
。此外,多配置和单配置生成器的技术可以而且应该组合起来,以在所有生成器类型上提供稳健的行为。最终结果看起来像这样:
The default value for CMAKE_BUILD_TYPE is an empty string, so the above would
cause a fatal error for both single and multi configuration generators unless
the developer explicitly set it. This is undesirable, especially for multi
configuration generators which don’t even use the CMAKE_BUILD_TYPE variable’s
value. This can be handled by having the project provide a default value if
CMAKE_BUILD_TYPE hasn’t been set. Furthermore, the techniques for multi and
single configuration generators can and should be combined to give robust
behavior across all generator types. The end result would look something like
this:
cmake_minimum_required(3.11)
project(Foo)
get_property(isMultiConfig GLOBAL
PROPERTY GENERATOR_IS_MULTI_CONFIG
)
if(isMultiConfig)
if(NOT "Profile" IN_LIST CMAKE_CONFIGURATION_TYPES)
list(APPEND CMAKE_CONFIGURATION_TYPES Profile)
endif()
else()
set(allowedBuildTypes Debug Release Profile)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
STRINGS "${allowedBuildTypes}"
)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
elseif(NOT CMAKE_BUILD_TYPE IN_LIST allowedBuildTypes)
message(FATAL_ERROR
"Unknown build type: ${CMAKE_BUILD_TYPE}"
)
endif()
endif()
# Set relevant Profile-specific flag variables as needed...cmake_minimum_required(3.11)
project(Foo)
get_property(isMultiConfig GLOBAL
PROPERTY GENERATOR_IS_MULTI_CONFIG
)
if(isMultiConfig)
if(NOT "Profile" IN_LIST CMAKE_CONFIGURATION_TYPES)
list(APPEND CMAKE_CONFIGURATION_TYPES Profile)
endif()
else()
set(allowedBuildTypes Debug Release Profile)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
STRINGS "${allowedBuildTypes}"
)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
elseif(NOT CMAKE_BUILD_TYPE IN_LIST allowedBuildTypes)
message(FATAL_ERROR
"Unknown build type: ${CMAKE_BUILD_TYPE}"
)
endif()
endif()
# Set relevant Profile-specific flag variables as needed...
上面讨论的所有技术仅允许选择自定义构建类型,它们不定义有关该构建类型的任何内容。从根本上讲,当选择构建类型时,它指定 CMake 应使用哪些特定于配置的变量,并且还会影响其逻辑取决于当前配置的任何生成器表达式(即 和$<CONFIG>)$<CONFIG:…>。这些变量和生成器表达式将在下一章中详细讨论,但目前主要关注以下两个变量系列:
All of the techniques discussed above merely allow a custom build type to be
selected, they don’t define anything about that build type. Fundamentally, when
a build type is selected, it specifies which configuration-specific variables
CMake should use and it also affects any generator expressions whose logic
depends on the current configuration (i.e. $<CONFIG> and $<CONFIG:…>).
These variables and generator expressions are discussed in detail in the next
chapter, but for now, the following two families of variables are of primary
interest:
CMAKE_<LANG>_FLAGS_<CONFIG>
CMAKE_<LANG>_FLAGS_<CONFIG>
CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG>
CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG>
这些可用于在不带后缀的同名变量提供的默认设置之上添加额外的编译器和链接器标志_<CONFIG>
。例如,自定义Profile构建类型的标志可以定义如下:
These can be used to add additional compiler and linker flags over and above
the default set provided by the same-named variables without the _<CONFIG>
suffix. For example, flags for a custom Profile build type could be defined
as follows:
set(CMAKE_C_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)
set(CMAKE_CXX_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)
set(CMAKE_EXE_LINKER_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)
set(CMAKE_STATIC_LINKER_FLAGS_PROFILE
""
CACHE STRING ""
)
set(CMAKE_MODULE_LINKER_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)set(CMAKE_C_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)
set(CMAKE_CXX_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)
set(CMAKE_EXE_LINKER_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)
set(CMAKE_STATIC_LINKER_FLAGS_PROFILE
""
CACHE STRING ""
)
set(CMAKE_MODULE_LINKER_FLAGS_PROFILE
"-p -g -O2"
CACHE STRING ""
)
上面假设使用 GCC 兼容的编译器来保持示例简单并打开分析以及启用调试符号和大多数优化。另一种方法是将编译器和链接器标志基于其他构建类型之一,并添加所需的额外标志。只要它出现在project()命令之后就可以完成此操作,因为该命令填充默认编译器和链接器标志变量。对于分析,RelWithDebInfo默认构建类型是一个很好的选择作为基本配置,因为它可以进行调试和大多数优化:
The above assumes a GCC-compatible compiler to keep the example simple and
turns on profiling as well as enabling debugging symbols and most
optimizations. An alternative is to base the compiler and linker flags on
one of the other build types and add the extra flags needed. This can be
done as long as it comes after the project() command, since that command
populates the default compiler and linker flag variables. For profiling,
the RelWithDebInfo default build type is a good one to choose as the base
configuration since it enables both debugging and most optimizations:
set(CMAKE_C_FLAGS_PROFILE
"${CMAKE_C_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)
set(CMAKE_CXX_FLAGS_PROFILE
"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)
set(CMAKE_EXE_LINKER_FLAGS_PROFILE
"${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE
"${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)
set(CMAKE_STATIC_LINKER_FLAGS_PROFILE
"${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}"
CACHE STRING ""
)
set(CMAKE_MODULE_LINKER_FLAGS_PROFILE
"${CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)set(CMAKE_C_FLAGS_PROFILE
"${CMAKE_C_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)
set(CMAKE_CXX_FLAGS_PROFILE
"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)
set(CMAKE_EXE_LINKER_FLAGS_PROFILE
"${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE
"${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)
set(CMAKE_STATIC_LINKER_FLAGS_PROFILE
"${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}"
CACHE STRING ""
)
set(CMAKE_MODULE_LINKER_FLAGS_PROFILE
"${CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO} -p"
CACHE STRING ""
)
每个自定义配置都应该定义关联的编译器和链接器标志变量。对于某些多配置生成器类型,CMake 将检查所需的变量是否存在,如果未设置,则会失败并显示错误。
Each custom configuration should have the associated compiler and linker flag variables defined. For some multi configuration generator types, CMake will check that the required variables exist and will fail with an error if they are not set.
有时可能为自定义构建类型定义的另一个变量是
CMAKE_<CONFIG>_POSTFIX。它用于初始化
<CONFIG>_POSTFIX每个库目标的属性,当为指定的配置构建时,其值将附加到此类目标的文件名中。这允许将多个构建类型的库放在同一目录中,而不会相互覆盖。CMAKE_DEBUG_POSTFIX通常设置为类似d或 的值_debug,特别是对于 Visual Studio 构建,其中必须使用不同的运行时 DLL 进行调试和非调试构建,因此包可能需要包含两种构建类型的库。对于Profile上面定义的自定义构建类型,示例可能是:
Another variable which may sometimes be defined for a custom build type is
CMAKE_<CONFIG>_POSTFIX. It is used to initialize the
<CONFIG>_POSTFIX property of each library target, with its value being
appended to the file name of such targets when built for the specified
configuration. This allows libraries from multiple build types to be put in the
same directory without overwriting each other. CMAKE_DEBUG_POSTFIX is often
set to values like d or _debug, especially for Visual Studio builds where
different runtime DLLs must be used for Debug and non-Debug builds, so
packages may need to include libraries for both build types. In the case of the
custom Profile build type defined above, an example might be:
set(CMAKE_PROFILE_POSTFIX _profile)set(CMAKE_PROFILE_POSTFIX _profile)
如果创建包含多个构建类型的包,
CMAKE_<CONFIG>_POSTFIX强烈建议为每个构建类型进行设置。按照惯例,发布版本的后缀通常为空。但请注意,<CONFIG>_POSTFIX目标属性在 Apple 平台上被忽略。
If creating packages that contain multiple build types, setting
CMAKE_<CONFIG>_POSTFIX for each build type is highly recommended. By
convention, the postfix for Release builds is typically empty. Note though that
the <CONFIG>_POSTFIX target property is ignored on Apple platforms.
由于历史原因,传递给命令的项目target_link_libraries()
可以使用debug或optimized关键字作为前缀,以指示指定的项目只能分别链接到调试或非调试版本。如果构建类型debug在DEBUG_CONFIGURATIONS全局属性中列出,则将其视为构建,否则将其视为optimized。对于自定义构建类型,如果在此场景中应将其视为构建,则应将其名称添加到此全局属性中debug。例如,如果一个项目定义了自己的自定义构建类型,
StrictChecker并且该构建类型应被视为非优化的调试构建类型,则它可以(并且应该)像这样明确说明:
For historical reasons, the items passed to the target_link_libraries()
command can be prefixed with the debug or optimized keywords to indicate
that the named item should only be linked in for debug or non-debug builds
respectively. A build type is considered to be a debug build if it is listed
in the DEBUG_CONFIGURATIONS global property, otherwise it is considered
to be optimized. For custom build types, they should have their name added to
this global property if they should be treated as a debug build in this
scenario. As an example, if a project defines its own custom build type called
StrictChecker and that build type should be considered a non-optimized debug
build type, it can (and should) make this clear like so:
set_property(GLOBAL APPEND PROPERTY
DEBUG_CONFIGURATIONS StrictChecker
)set_property(GLOBAL APPEND PROPERTY
DEBUG_CONFIGURATIONS StrictChecker
)
新项目通常应该更喜欢使用生成器表达式,而不是
命令中的debug和optimized关键字target_link_libraries()。下一章将更详细地讨论这个领域。
New projects should normally prefer to use generator expressions instead of the
debug and optimized keywords with the target_link_libraries() command.
The next chapter discusses this area in more detail.
开发人员不应假设使用特定的 CMake 生成器来构建他们的项目。同一项目的另一个开发人员可能更喜欢使用不同的生成器,因为它与他们的 IDE 工具集成得更好,或者 CMake 的未来版本可能会添加对新生成器类型的支持,这可能会带来其他好处。某些构建工具可能包含错误,项目稍后可能会受到影响,因此在修复此类错误之前,可以使用替代生成器来依靠。如果假设使用特定的 CMake 生成器,则扩展项目的支持平台集也会受到阻碍。
Developers should not assume a particular CMake generator is being used to build their project. Another developer on the same project may prefer to use a different generator because it integrates better with their IDE tool, or a future version of CMake may add support for a new generator type which might bring other benefits. Certain build tools may contain bugs which a project may later be affected by, so it can be useful to have alternative generators to fall back on until such bugs are fixed. Expanding a project’s set of supported platforms can also be hindered if a particular CMake generator has been assumed.
当使用像 Makefiles 或 Ninja 这样的单一配置生成器时,请考虑使用多个构建目录,每个目录对应一种感兴趣的构建类型。这允许在构建类型之间切换,而无需每次都强制完全重新编译。这提供了与多配置生成器固有提供的行为类似的行为,并且可以成为启用 Qt Creator 等 IDE 工具来模拟多配置功能的有用方法。
When using single configuration generators like Makefiles or Ninja, consider using multiple build directories, one for each build type of interest. This allows switching between build types without forcing a complete recompile each time. This provides similar behavior to that inherently offered by multi configuration generators and can be a useful way to enable IDE tools like Qt Creator to simulate multi configuration functionality.
对于单个配置生成器,CMAKE_BUILD_TYPE如果为空,请考虑设置为更好的默认值。虽然空构建类型在技术上是有效的,但开发人员也经常将其误解为调试构建而不是其自己独特的构建类型。此外,CMAKE_BUILD_TYPE除非首先确认正在使用单个配置生成器,否则请避免创建逻辑。即使这样,这样的逻辑也可能是脆弱的,并且可能可以使用生成器表达式来更通用和更稳健地表达。
For single configuration generators, consider setting CMAKE_BUILD_TYPE to a
better default value if it is empty. While an empty build type is technically
valid, it is also often misunderstood by developers to mean a Debug build
rather than its own distinct build type. Furthermore, avoid creating logic
based on CMAKE_BUILD_TYPE unless it is first confirmed that a single
configuration generator is being used. Even then, such logic is likely to
be fragile and could probably be expressed with more generality and robustness
using generator expressions instead.
CMAKE_CONFIGURATION_TYPES仅当已知正在使用多配置生成器或变量已存在时才考虑修改变量。如果添加自定义构建类型或删除默认构建类型之一,请不要修改缓存变量,而是更改同名的常规变量(它将优先于缓存变量)。还喜欢添加和删除单个项目而不是完全替换列表。这两种措施都将有助于避免干扰开发人员对缓存变量所做的更改。
Only consider modifying the CMAKE_CONFIGURATION_TYPES variable if it is known
that a multi configuration generator is being used or if the variable already
exists. If adding a custom build type or removing one of the default build
types, do not modify the cache variable but instead change the regular variable
of the same name (it will take precedence over the cache variable). Also prefer
to add and remove individual items rather than completely replacing the list.
Both of these measures will help avoid interfering with changes made to the
cache variable by the developer.
如果需要 CMake 3.9 或更高版本,请使用GENERATOR_IS_MULTI_CONFIG全局属性来明确查询生成器类型,而不是依靠 的存在CMAKE_CONFIGURATION_TYPES来执行不太稳健的检查。
If requiring CMake 3.9 or later, use the GENERATOR_IS_MULTI_CONFIG global
property to definitively query the generator type instead of relying on the
existence of CMAKE_CONFIGURATION_TYPES to perform a less robust check.
一种常见但不正确的做法是查询LOCATION目标属性以计算出目标的输出文件名。一个相关的错误是在自定义命令中假设特定的构建输出目录结构(请参阅
第 18 章,自定义任务)。这些方法并不适用于所有构建类型,因为
LOCATION在配置时对于多配置生成器是未知的,并且构建输出目录结构在各种 CMake 生成器类型中通常是不同的。$<TARGET_FILE:…>
应该使用类似的生成器表达式,因为它们为所有生成器提供了可靠的所需路径,无论它们是单配置还是多配置。
A common but incorrect practice is to query the LOCATION target property
to work out a target’s output file name. A related error is to assume a
particular build output directory structure in custom commands (see
Chapter 18, Custom Tasks). These methods do not work for all build types, since
LOCATION is not known at configure time for multi configuration generators
and the build output directory structure is typically different across the
various CMake generator types. Generator expressions like $<TARGET_FILE:…>
should be used instead, as they robustly provide the required path for all
generators, whether they be single or multi configuration.
上一章讨论了构建类型以及它与选择一组特定的编译器和链接器行为的关系。本章讨论如何控制编译器和链接器行为的基础知识。这里提供的材料涵盖了每个 CMake 开发人员都应该熟悉的一些最重要的主题和技术。
The previous chapter discussed the build type and how it relates to selecting a particular set of compiler and linker behavior. This chapter discusses the fundamentals of how that compiler and linker behavior is controlled. The material presented here covers some of the most important topics and techniques with which every CMake developer should become familiar.
在继续之前,请注意,随着 CMake 的发展,用于控制编译器和链接器行为的可用方法也得到了改进。重点已从更加构建全局的视图转移到可以控制每个单独目标的需求以及这些需求应该或不应该如何应用于依赖于它的其他目标的视图。这是思维上的一个重要转变,因为它影响项目如何最有效地定义目标的构建方式。CMake 更成熟的功能可用于粗略地控制行为,但代价是失去定义目标之间关系的能力。相反,应该首选更新的以目标为中心的功能,因为它们极大地提高了构建的稳健性,并提供了对编译器和链接器行为的更精确的控制。新功能的行为和使用方式也往往更加一致。
Before proceeding, note that as CMake has evolved, the available methods for controlling the compiler and linker behavior have also improved. The focus has shifted from a more build-global view to one where the requirements of each individual target can be controlled, along with how those requirements should or should not be applied to other targets that depend on it. This is an important shift in thinking, as it affects how a project can most effectively define the way targets should be built. CMake’s more mature features can be used to control behavior at a coarse level at the expense of losing the ability to define relationships between targets. The more recent target-focused features should be preferred instead, since they greatly improve the robustness of the build and offer much more precise control over compiler and linker behavior. The newer features also tend to be more consistent in their behavior and the way they are meant to be used.
在 CMake 的属性系统中,目标属性构成了控制编译器和链接器标志的主要机制。某些属性提供指定任意标志的能力,而其他属性则专注于特定功能,以便它们可以抽象出平台或编译器差异。本章重点介绍更常用和通用的属性,后面的章节将介绍一些更具体的属性。
Within CMake’s property system, the target properties form the primary mechanism by which compiler and linker flags are controlled. Some properties provide the ability to specify any arbitrary flag, whereas others focus on a specific capability so they can abstract away platform or compiler differences. This chapter focuses on the more commonly used and general purpose properties, with later chapters covering a number of the more specific ones.
在继续之前,应该注意的是,以下各节中讨论的目标属性通常不会直接修改。CMake 提供了专用命令,这些命令通常比直接属性操作更方便、更强大。尽管如此,了解所涉及的底层属性可以帮助开发人员了解这些命令的一些功能和限制。
Before proceeding, it should be noted that the target properties discussed in the following sections are not usually modified directly. CMake provides dedicated commands which are generally more convenient and more robust than direct property manipulation. Nevertheless, understanding the underlying properties involved can help developers understand some of the features and restrictions of those commands.
用于控制编译器标志的最基本的目标属性如下,每个属性都包含一个项目列表:
The most fundamental target properties for controlling compiler flags are the following, each of which hold a list of items:
INCLUDE_DIRECTORIES
INCLUDE_DIRECTORIES
-I为 或
/I)。创建目标时,该目标属性的初始值取自同名的目录属性。
-I or
/I). When a target is created, the initial value of this target property is
taken from the directory property of the same name.
COMPILE_DEFINITIONS
COMPILE_DEFINITIONS
VAR或VAR=VALUE,CMake 会将其转换为适合所使用的编译器的形式(通常-DVAR…为 或/DVAR…)。创建目标时,该目标属性的初始值为空。有一个同名的目录属性,但它不用于为此目标属性提供初始值。相反,目录和目标属性在最终的编译器命令行中组合。
VAR or VAR=VALUE, which
CMake will convert to the appropriate form for the compiler being used
(typically -DVAR… or /DVAR…). When a target is created, the initial
value of this target property will be empty. There is a directory property of
the same name, but it is not used to provide an initial value for this target
property. Rather, the directory and target properties are combined in the
final compiler command line.
COMPILE_OPTIONS
COMPILE_OPTIONS
和属性实际上只是为了方便,负责处理项目经常想要设置的最常见事物的编译器特定标志INCLUDE_DIRECTORIES。COMPILE_DEFINITIONS然后在属性中提供所有剩余的编译器特定标志COMPILE_OPTIONS。
The INCLUDE_DIRECTORIES and COMPILE_DEFINITIONS properties are really just
conveniences, taking care of the compiler specific flags for the most common
things projects often want to set. All remaining compiler specific flags are
then provided in the COMPILE_OPTIONS property.
上面的三个目标属性中的每一个都有一个具有相同名称但带有INTERFACE_前缀的相关目标属性。这些接口属性执行完全相同的操作,只不过它们不应用于目标本身,而是应用于直接链接到它的任何其他目标。换句话说,它们用于指定消费目标应继承的编译器标志。因此,它们通常被称为使用需求,而不是INTERFACE有时被称为构建需求的非属性。两种特殊的库类型IMPORTED稍后将在第 17 章目标类型INTERFACE中讨论
。这些特殊库类型仅支持
目标属性,而不支持非属性。INTERFACE_…INTERFACE_…
Each of the three target properties above has a related target property with
the same name, but with INTERFACE_ prepended. These interface properties do
exactly the same thing, except instead of applying to the target itself, they
apply to any other target which links directly to it. In other words, they are
used to specify compiler flags which consuming targets should inherit. For
this reason, they are often referred to as usage requirements, in contrast to
the non-INTERFACE properties which are sometimes called build requirements.
Two special library types IMPORTED and INTERFACE are discussed later in
Chapter 17, Target Types. These special library types support only the INTERFACE_…
target properties and not the non-INTERFACE_… properties.
与非接口对应项不同,上述INTERFACE_…
属性都不是从目录属性初始化的。相反,它们都是从空开始的,因为只有项目知道哪些标头搜索路径、定义和编译器标志应该传播到消费目标。
Unlike their non-interface counterparts, none of the above INTERFACE_…
properties are initialized from directory properties. They instead all start out
empty, since only the project has knowledge of what header search paths,
defines and compiler flags should propagate to consuming targets.
除 之外COMPILE_FLAGS,上述所有目标属性都支持生成器表达式。生成器表达式对于该属性特别有用COMPILE_OPTIONS
,因为它仅在满足某些条件时才允许添加特定标志,例如仅针对一个特定编译器。
With the exception of COMPILE_FLAGS, all of the above target properties
support generator expressions.
Generator expressions are particularly useful for the COMPILE_OPTIONS
property, since it enables adding a particular flag only if some condition is
met, such as only for one particular compiler.
如果需要在单个源文件级别操作编译器标志,则目标属性不够精细。对于这种情况,CMake 提供了
COMPILE_DEFINITIONS、COMPILE_FLAGS和COMPILE_OPTIONS源文件属性(COMPILE_OPTIONS源文件属性仅在 CMake 3.11 中添加)。这些属性都类似于它们的同名目标属性,只不过它们仅适用于设置它们的单个源文件。请注意,它们对生成器表达式的支持落后于目标属性,COMPILE_DEFINITIONS源文件属性在 CMake 3.8 中获得了生成器表达式支持,而其他属性则在 3.11 中获得了支持。此外,Xcode 项目文件格式根本不支持配置特定的源文件属性,因此如果针对 Apple 平台,$<CONFIG>则
$<CONFIG:…>不应在源文件属性中使用。另请记住第 9.5 节“源属性”中讨论的有关使用源文件属性时导致性能问题的实现细节的警告。
If compiler flags need to be manipulated at the individual source file level,
target properties are not granular enough. For such cases, CMake provides the
COMPILE_DEFINITIONS, COMPILE_FLAGS and COMPILE_OPTIONS source file
properties (the COMPILE_OPTIONS source file property was only added in CMake
3.11). These are each analogous to their same-named target properties except
that they apply only to the individual source file on which they are set. Note
that their support for generator expressions has lagged behind that of the
target properties, with the COMPILE_DEFINITIONS source file property gaining
generator expression support in CMake 3.8 and the others in 3.11. Furthermore,
the Xcode project file format does not support configuration specific source
file properties at all, so if targeting Apple platforms, $<CONFIG> or
$<CONFIG:…> should not be used in source file properties. Also keep in mind
the warnings discussed back in Section 9.5, “Source Properties” regarding implementation
details leading to performance issues when source file properties are used.
与链接器标志关联的目标属性与编译器标志的目标属性相似,但有些属性仅在较新的 CMake 版本中添加。CMake 3.13 特别添加了许多针对链接器控制的改进。另请注意,只有某些与链接器相关的属性具有关联的接口属性,并且并非所有属性都支持生成器表达式。
The target properties associated with linker flags have similarities to those for compiler flags, but some were only added in more recent CMake versions. CMake 3.13 in particular added a number of improvements for linker control. Also note that only some of the linker-related properties have an associated interface property and that not all properties support generator expressions.
LINK_LIBRARIES
LINK_LIBRARIES
INTERFACE_LINK_LIBRARIES支持关联的接口属性。列出的每个库可以是以下之一:
lib)或后缀(例如.a, .so,
.dll)。
CMake 将使用适当的链接器标志来链接属性中列出的每个项目
LINK_LIBRARIES。在某些情况下,链接器标志也可能出现在该属性中,但通常首选下面的其他目标属性来保存此类选项。
INTERFACE_LINK_LIBRARIES is supported.
Each library listed can be one of the following:
lib) or suffix (e.g. .a, .so,
.dll).
CMake will use the appropriate linker flags to link each item listed in the
LINK_LIBRARIES property.
In some circumstances, linker flags may also be present in this property, but
the other target properties below are generally preferred for holding such
options.
LINK_OPTIONS
LINK_OPTIONS
INTERFACE_LINK_OPTIONS还支持关联的接口属性。INTERFACE_LINK_OPTIONS请注意,即使设置的目标是静态库,此接口属性的内容也将应用于使用目标。这是因为接口属性指定了
使用者应使用的链接器标志,因此所使用的库的类型不是一个因素。
An associated interface property INTERFACE_LINK_OPTIONS is also supported.
Note that the contents of this interface property will be applied to consuming
targets even if the target on which INTERFACE_LINK_OPTIONS is set is a
static library.
This is because the interface property is specifying linker flags that the
consumer should use, so the type of the library being consumed is not a
factor.
LINK_FLAGS
LINK_FLAGS
LINK_OPTIONS,但也有许多不同之处。第一个关键区别是它保存将直接放置在链接器命令行上的单个字符串,而不是链接器标志列表。另一个区别是它不支持生成器表达式。此外,没有关联的接口属性,并且在创建目标时将其初始化为空值。一般来说,LINK_OPTIONS它更强大并提供更广泛的功能,因此仅LINK_FLAGS在必须支持早于 3.13 的 CMake 版本时才使用。
LINK_OPTIONS, but there are a
number of differences.
The first key difference is that it holds a single string that will be placed
directly on the linker command line rather than a list of linker flags.
Another difference is that it does not support generator expressions.
Furthermore, there is no associated interface property and it is initialized
to an empty value when the target is created.
In general, LINK_OPTIONS is more robust and offers a broader set of features,
so only use LINK_FLAGS if CMake versions earlier than 3.13 must be supported.
STATIC_LIBRARY_OPTIONS
STATIC_LIBRARY_OPTIONS
LINK_OPTIONS。它仅对构建为静态库的目标有意义,并将用于库管理器或归档器工具。它包含一个选项列表,支持生成器表达式,并且需要进行重复数据删除(请参见第 15.3 节“重复数据删除选项”)。与 一样LINK_OPTIONS,仅在 CMake 3.13 中添加了对 的支持STATIC_LIBRARY_OPTIONS,但请注意,没有关联的接口属性。目标不能指定其使用者的图书馆员/归档器标志,只能指定链接器标志(请参阅INTERFACE_LINK_OPTIONS上面的评论)。
LINK_OPTIONS.
It only has meaning for targets being built as a static library and will be
used for the librarian or archiver tool.
It holds a list of options, generator expressions are supported and it is
subject to de-duplication (see Section 15.3, “De-duplicating Options”).
Like LINK_OPTIONS, support for STATIC_LIBRARY_OPTIONS was only added in
CMake 3.13, but note that there is no associated interface property.
A target cannot dictate librarian/archiver flags of its consumers, only linker
flags (see the comments regarding INTERFACE_LINK_OPTIONS above).
STATIC_LIBRARY_FLAGS
STATIC_LIBRARY_FLAGS
LINK_FLAGS,并且仅在必须支持早于 3.13 的 CMake 版本时才应使用。它是单个字符串而不是列表,并且不支持生成器表达式。没有关联的接口属性。
LINK_FLAGS and should only be used if CMake
versions earlier than 3.13 must be supported.
It is a single string rather than a list and it does not support generator
expressions.
There is no associated interface property.
在一些较旧的项目中,人们可能偶尔会遇到名为 的目标属性
LINK_INTERFACE_LIBRARIES,它是旧版本的
INTERFACE_LINK_LIBRARIES. 自 CMake 2.8.12 起,此旧属性已被弃用,但CMP0022如果需要,可以使用策略来赋予旧属性优先权。新项目应该优先使用INTERFACE_LINK_LIBRARIES。
In some older projects, one may occasionally encounter a target property named
LINK_INTERFACE_LIBRARIES, which is an older version of
INTERFACE_LINK_LIBRARIES. This older property has been deprecated since CMake
2.8.12, but policy CMP0022 can be used to give the old property precedence if
needed. New projects should prefer to use INTERFACE_LINK_LIBRARIES instead.
LINK_FLAGS和属性STATIC_LIBRARY_FLAGS不支持生成器表达式。但是,它们确实具有相关的特定于配置的属性:
The LINK_FLAGS and STATIC_LIBRARY_FLAGS properties do not support generator
expressions. They do, however, have related configuration-specific properties:
LINK_FLAGS_<CONFIG>
LINK_FLAGS_<CONFIG>
STATIC_LIBRARY_FLAGS_<CONFIG>
STATIC_LIBRARY_FLAGS_<CONFIG>
<CONFIG>当与正在构建的配置匹配时,除了非特定于配置的标志之外,还将使用这些标志。仅当项目必须支持 3.13 之前的 CMake 版本时才应使用这些。对于 3.13 或更高版本,更喜欢使用LINK_OPTIONS和STATIC_LIBRARY_OPTIONS并使用生成器表达式来表达特定于配置的内容。
These flags will be used in addition to the non-configuration-specific flags
when the <CONFIG> matches the configuration being built. These should only
be used if the project must support CMake versions earlier than 3.13. For
3.13 or later, prefer to use LINK_OPTIONS and STATIC_LIBRARY_OPTIONS and
express configuration-specific content using generator expressions.
将标志传递给链接器的困难之一是链接器通常是通过编译器前端调用的,但每个编译器对于如何传递链接器选项都有自己的语法。例如,调用ld链接器 viagcc需要使用 form 指定链接器标志-Wl,…,而clang需要使用 form
-Xlinker …。使用 CMake 3.13 或更高版本,可以通过向和
属性LINKER:中的每个链接器标志添加前缀来自动处理这种差异。这将导致链接器标志被转换为所使用的编译器前端所需的形式。例如:LINK_OPTIONSINTERFACE_LINK_OPTIONS
One of the difficulties of passing flags to the linker is that the linker is
usually invoked via the compiler front end, but each compiler has its own
syntax for how to pass through linker options.
For example, invoking the ld linker via gcc requires specifying linker
flags using the form -Wl,…, whereas clang expects the form
-Xlinker ….
With CMake 3.13 or later, this difference can be handled automatically by
adding a LINKER: prefix to each linker flag in the LINK_OPTIONS and
INTERFACE_LINK_OPTIONS properties.
This will result in the linker flag being transformed into the required form
for the compiler front end being used.
For example:
set_target_properties(Foo PROPERTIES
LINK_OPTIONS LINKER:-stats
)set_target_properties(Foo PROPERTIES
LINK_OPTIONS LINKER:-stats
)
使用gcc编译器,这会添加-Wl,-stats,而使用clang它会添加-Xlinker -stats。有关前缀的进一步相关讨论,请参阅第 15.3 节“去重选项”LINKER:
。
Using the gcc compiler, this would add -Wl,-stats, whereas with clang it
would add -Xlinker -stats.
See Section 15.3, “De-duplicating Options” for further related discussion of the LINKER:
prefix.
如前所述,上述目标属性通常不直接操作。CMake 提供了专用命令,以更方便、更强大的方式修改它们,这也鼓励明确规范目标之间的依赖关系和传递行为。回到第 4.3 节“链接目标”,介绍了该命令,并解释了如何使用、和规范target_link_libraries()来表达目标间依赖关系。之前的讨论主要集中在目标之间的依赖关系,但是在上面对目标属性的讨论之后,现在可以使这些关键字的确切效果更加精确。PRIVATEPUBLICINTERFACE
As mentioned earlier, the above target properties are not normally manipulated
directly.
CMake provides dedicated commands for modifying them in a more convenient and
robust manner which also encourage clear specification of dependencies and
transitive behavior between targets.
Back in Section 4.3, “Linking Targets”, the target_link_libraries() command was
presented, along with an explanation of how inter-target dependencies are
expressed using PRIVATE, PUBLIC and INTERFACE specifications.
That earlier discussion focused on the dependency relationships between
targets, but following the above discussion of target properties, the exact
effects of those keywords can now be made more precise.
target_link_libraries(targetName
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)target_link_libraries(targetName
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)
PRIVATE
PRIVATE
PRIVATE仅影响其自身的行为targetName。这些项目将添加到LINK_LIBRARIES目标属性中。
PRIVATE only affect the behavior of targetName itself.
The items are added to the LINK_LIBRARIES target property.
INTERFACE
INTERFACE
PRIVATE,关键字后面的项目INTERFACE
将添加到目标的INTERFACE_LINK_LIBRARIES属性中。链接到的任何目标都targetName将应用这些项目,就像这些项目列在其自己的LINK_LIBRARIES属性中一样。
PRIVATE, with items following the INTERFACE
keyword being added to the target’s INTERFACE_LINK_LIBRARIES property.
Any target that links to targetName will have these items applied to them as
though the items were listed in their own LINK_LIBRARIES property.
PUBLIC
PUBLIC
PRIVATE和的效果INTERFACE。
PRIVATE and INTERFACE.
大多数时候,开发人员可能会发现
第 4.3 节“链接目标”中的解释更直观,但是上面更精确的描述可以帮助解释更复杂的项目中的行为,在这些项目中,属性可能会以不寻常的方式进行操作。target_…()上述描述也恰好与操作编译器和链接器标志的其他命令的行为非常接近。事实上,它们都遵循相同的模式并以相同的方式
应用PRIVATE,PUBLIC和
关键字。INTERFACE
Most of the time, developers will probably find the explanation in
Section 4.3, “Linking Targets” more intuitive, but the above more precise description
can help explain the behavior in more complex projects where properties may be
manipulated in unusual ways.
The above description also happens to map very closely to the behavior of the
other target_…() commands which manipulate compiler and linker flags.
In fact, they all follow the same pattern and apply the PRIVATE, PUBLIC and
INTERFACE keywords in the same way.
target_link_options(targetName [BEFORE]
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)target_link_options(targetName [BEFORE]
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)
该target_link_options()命令是在 CMake 3.13 中添加的。target_link_libraries()它允许项目更清晰、准确地传达正在添加的链接器选项,而不是在 中指定链接器选项。它还避免LINK_LIBRARIES使用链接器标志填充属性,而是填充为此类标志预留的相关目标属性。该target_link_options()命令用项目填充LINK_OPTIONS目标属性PRIVATE,并INTERFACE_LINK_OPTIONS用INTERFACE项目填充目标属性。正如人们所期望的那样,PUBLIC项目被添加到两个目标属性中。由于这些属性支持生成器表达式,因此
target_link_options()命令也支持。
The target_link_options() command was added in CMake 3.13.
Instead of specifying linker options in target_link_libraries(), it allows
projects to more clearly and accurately communicate that linker options are
being added.
It also avoids populating the LINK_LIBRARIES property with linker flags and
instead populates the relevant target properties set aside for such flags.
The target_link_options() command populates the LINK_OPTIONS target
property with the PRIVATE items and the INTERFACE_LINK_OPTIONS target
property with the INTERFACE items.
As one would expect, PUBLIC items are added to both target properties.
Since these properties support generator expressions, so does the
target_link_options() command.
通常,每次target_link_options()调用时,指定的项目都会附加到相关的目标属性中。这使得以自然、渐进的方式添加多个选项变得很容易。如果需要,BEFORE可以使用关键字将列出的选项添加到目标属性的现有内容之前。
Normally, each time target_link_options() is called, the specified items
are appended to the relevant target properties.
This makes it easy to add multiple options in a natural, progressive manner.
If required, the BEFORE keyword can be used to prepend the listed options to
existing contents of the target properties instead.
即使targetName是静态库,也可以使用此命令,但请注意,PRIVATE和PUBLIC项目将填充LINK_OPTIONS,而不是
STATIC_LIBRARY_OPTIONS。为了填充,目前除了直接使用或STATIC_LIBRARY_OPTIONS修改目标属性之外没有其他选择
。使用将项目添加到静态库目标仍然有用,因为 的内容将应用于使用目标。set_property()set_target_properties()target_link_options()INTERFACEINTERFACE_LINK_OPTIONS
This command can be used even if targetName is a static library, but note
that PRIVATE and PUBLIC items will populate LINK_OPTIONS, not
STATIC_LIBRARY_OPTIONS.
In order to populate STATIC_LIBRARY_OPTIONS, there is currently no
alternative other than to modify the target property directly with
set_property() or set_target_properties().
Using target_link_options() to add INTERFACE items to a static library
target is still useful, since the contents of INTERFACE_LINK_OPTIONS are
applied to the consuming target.
由于target_link_options()将项目添加到LINK_OPTIONS和
INTERFACE_LINK_OPTIONS属性,该命令还支持以 为前缀的项目LINKER:来处理编译器前端差异。因此,上一节中的示例可以更好地实现为:
Since target_link_options() adds items to the LINK_OPTIONS and
INTERFACE_LINK_OPTIONS properties, the command also supports items being
prefixed with LINKER: to handle the compiler front end differences.
The example from the previous section can therefore be better implemented as:
target_link_options(Foo PRIVATE LINKER:-stats)target_link_options(Foo PRIVATE LINKER:-stats)
有许多命令可用于管理目标的编译器相关属性。
A number of commands are available for managing a target’s compiler-related properties.
target_include_directories(targetName [AFTER|BEFORE] [SYSTEM]
<PRIVATE|PUBLIC|INTERFACE> dir1 [dir2 ...]
[<PRIVATE|PUBLIC|INTERFACE> dir3 [dir4 ...]]
...
)target_include_directories(targetName [AFTER|BEFORE] [SYSTEM]
<PRIVATE|PUBLIC|INTERFACE> dir1 [dir2 ...]
[<PRIVATE|PUBLIC|INTERFACE> dir3 [dir4 ...]]
...
)
该target_include_directories()命令将标头搜索路径添加到
INCLUDE_DIRECTORIES和INTERFACE_INCLUDE_DIRECTORIEStarget 属性。关键字后面的目录PRIVATE将添加到
INCLUDE_DIRECTORIES目标属性,而
INTERFACE关键字后面的目录将添加到INTERFACE_INCLUDE_DIRECTORIES目标属性。关键字后面的目录PUBLIC将添加到两者中。该BEFORE关键字与 for 具有相同的效果target_link_options(),导致指定的目录被添加到相关属性的前面而不是附加。CMake 3.20 添加了对对称关键字的支持AFTER,但不需要使用它,因为附加已经是默认行为。
The target_include_directories() command adds header search paths to the
INCLUDE_DIRECTORIES and INTERFACE_INCLUDE_DIRECTORIES target properties.
Directories following a PRIVATE keyword are added to the
INCLUDE_DIRECTORIES target property, while directories following an
INTERFACE keyword are added to the INTERFACE_INCLUDE_DIRECTORIES target
property. Directories following a PUBLIC keyword are added to both.
The BEFORE keyword has the same effect as for target_link_options(),
causing the specified directories to be prepended to the relevant properties
instead of appending.
CMake 3.20 added support for the AFTER keyword for symmetry, but it doesn’t
need to be used since appending is the default behavior already.
如果SYSTEM指定了关键字,编译器会将列出的目录视为某些平台上的系统包含路径。这样做的影响可能包括跳过某些编译器警告或更改文件依赖项的处理方式。它还可能影响某些编译器搜索头文件路径的顺序。开发人员有时会试图
SYSTEM消除来自标头的警告,而不是直接解决这些警告。如果此类标头是项目的一部分,SYSTEM则通常不是合适的选项。一般来说,SYSTEM用于项目外部的路径,但即使如此,也很少需要它。
If the SYSTEM keyword is specified, the compiler will treat the listed
directories as system include paths on some platforms. The effects of this can
include skipping certain compiler warnings or changing how the file
dependencies are handled. It can also affect the order in which header paths
are searched for some compilers. Developers are sometimes tempted to use
SYSTEM to silence warnings coming from headers rather than addressing those
warnings directly. If such headers are part of the project, SYSTEM is not
typically an appropriate option to use. In general, SYSTEM is intended for
paths outside of the project, but even then it should rarely be needed.
还值得注意的是,由导入目标的
INTERFACE_INCLUDE_DIRECTORIES属性指定的路径将被使用目标视为SYSTEM默认路径。这是因为导入的目标被假定来自项目外部,因此它们关联的标头应该以与其他系统提供的标头类似的方式处理。项目可以通过将使用
目标的NO_SYSTEM_FROM_IMPORTED属性设置为 true 来覆盖此行为,这将防止它使用的所有导入目标被视为SYSTEM. 第 17 章“目标类型”详细介绍了导入的目标。
It is also worth noting that paths specified by an imported target’s
INTERFACE_INCLUDE_DIRECTORIES property will be treated by consuming targets
as though they were SYSTEM paths by default. This is because imported targets
are assumed to be coming from outside the project and therefore their
associated headers should be treated in a similar way to other system-provided
headers. The project can override this behavior by setting the consuming
target’s NO_SYSTEM_FROM_IMPORTED property to true, which will prevent all
of the imported targets it consumes from being treated as SYSTEM. Imported
targets are covered in detail in Chapter 17, Target Types.
target_include_directories()与直接操作目标属性相比,该命令还有另一个优点。项目也可以指定相对目录,而不仅仅是绝对目录。相对路径将在需要时自动转换为绝对路径(下面讨论的一个例外),路径被视为相对于当前源目录。
The target_include_directories() command offers another advantage over
manipulating the target properties directly. Projects can specify relative
directories too, not just absolute directories. Relative paths will be
automatically converted to absolute paths where needed (with one exception
discussed below), with paths being treated as relative to the current source
directory.
由于该target_include_directories()命令基本上只是填充相关的目标属性,因此这些属性的所有常用功能都适用。特别是,可以使用生成器表达式,这一功能在安装目标和创建包时变得更加重要。和
生成器表达式允许为构建和$<BUILD_INTERFACE:…>安装$<INSTALL_INTERFACE:…>指定不同的路径。对于已安装的目标,通常使用相对路径,它们将被解释为相对于基本安装位置而不是源目录。第 26.2.1 节“接口属性”更详细地介绍了指定标头搜索路径的这个方面。对于构建目标,生成器表达式的扩展
$<BUILD_INTERFACE:…>发生在检查相对路径之后,因此此类表达式必须计算为绝对路径,否则 CMake 将发出错误。
Since the target_include_directories() command is basically just populating
the relevant target properties, all the usual features of those properties
apply. In particular, generator expressions can be used, a feature which
becomes much more important when installing targets and creating packages. The
$<BUILD_INTERFACE:…> and $<INSTALL_INTERFACE:…> generator expressions
allow different paths to be specified for building and installing. For
installed targets, relative paths are normally used and they would be
interpreted as relative to the base install location rather than the source
directory. Section 26.2.1, “Interface Properties” covers this aspect of specifying header
search paths in more detail. For building targets, the expansion of the
$<BUILD_INTERFACE:…> generator expression takes place after the check for
relative paths, so such expressions must evaluate to an absolute path or
CMake will issue an error.
target_compile_definitions(targetName
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)target_compile_definitions(targetName
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)
该target_compile_definitions()命令非常简单,每个项目都具有VAR或 的形式VAR=VALUE。PRIVATE项目填充
COMPILE_DEFINITIONS目标属性,而INTERFACE项目填充
INTERFACE_COMPILE_DEFINITIONS目标属性。PUBLIC项目填充两个目标属性。可以使用生成器表达式,但通常不需要以不同的方式处理构建和安装情况。
The target_compile_definitions() command is quite straightforward, with each
item having the form VAR or VAR=VALUE. PRIVATE items populate the
COMPILE_DEFINITIONS target property, while INTERFACE items populate the
INTERFACE_COMPILE_DEFINITIONS target property. PUBLIC items populate both
target properties. Generator expressions can be used, but there would usually
be no need to handle build and install situations differently.
target_compile_options(targetName [BEFORE]
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)target_compile_options(targetName [BEFORE]
<PRIVATE|PUBLIC|INTERFACE> item1 [item2 ...]
[<PRIVATE|PUBLIC|INTERFACE> item3 [item4 ...]]
...
)
该target_compile_options()命令也非常简单。每个项目都被视为编译器选项,其中PRIVATE项目填充
COMPILE_OPTIONS目标属性,INTERFACE项目填充
INTERFACE_COMPILE_OPTIONS目标属性。像往常一样,PUBLIC项目填充两个目标属性。对于所有情况,每个项目都会附加到现有目标属性值,但BEFORE可以使用关键字作为前缀。所有情况都支持生成器表达式,并且通常不需要以不同的方式处理构建和安装情况。
The target_compile_options() command is also quite straightforward. Each
item is treated as a compiler option, with PRIVATE items populating the
COMPILE_OPTIONS target property and INTERFACE items populating the
INTERFACE_COMPILE_OPTIONS target property. As usual, PUBLIC items populate
both target properties. For all cases, each item is appended to existing target
property values, but the BEFORE keyword can be used to prepend instead.
Generator expressions are supported in all cases and there would usually be
no need to handle build and install situations differently.
在 CMake 3.0 及更高版本中,目标属性是指定编译器和链接器标志的首选,因为它们能够定义它们如何与相互链接的目标交互。在 CMake 的早期版本中,目标属性不太突出,并且通常在目录级别指定属性。这些目录属性和通常用于操作它们的命令缺乏基于目标的等效项所显示的一致性,这是项目通常应尽可能避免使用它们的另一个原因。也就是说,由于许多在线教程和示例仍然使用它们,开发人员至少应该了解目录级属性和命令。
With CMake 3.0 and later, target properties are strongly preferred for specifying compiler and linker flags due to their ability to define how they interact with targets that link to one another. In earlier versions of CMake, target properties were much less prominent and properties were often specified at the directory level instead. These directory properties and the commands typically used to manipulate them lack the consistency shown by their target-based equivalents, which is another reason they should generally be avoided by projects where possible. That said, since many online tutorials and examples still use them, developers should at least be aware of the directory level properties and commands.
include_directories([AFTER | BEFORE] [SYSTEM]
dir1 [dir2...]
)include_directories([AFTER | BEFORE] [SYSTEM]
dir1 [dir2...]
)
简单地说,该include_directories()命令将标头搜索路径添加到在当前目录范围及以下目录范围内创建的目标。默认情况下,路径会附加到现有的目录列表中,但可以通过将变量设置CMAKE_INCLUDE_DIRECTORIES_BEFORE为 来更改默认值ON。BEFORE还可以使用和选项在每个调用的基础上进行控制AFTER,以明确指示应如何处理该调用的路径。项目应该对设置保持警惕CMAKE_INCLUDE_DIRECTORIES_BEFORE,因为大多数开发人员可能会认为附加目录的默认行为将适用。该SYSTEM关键字与
target_include_directories()命令具有相同的效果。
Simplistically, the include_directories() command adds header search paths to
targets created in the current directory scope and below. By default, paths are
appended to the existing list of directories, but that default can be changed
by setting the CMAKE_INCLUDE_DIRECTORIES_BEFORE variable to ON. It can
also be controlled on a per call basis with the BEFORE and AFTER options to
explicitly direct how the paths for that call should be handled. Projects
should be wary about setting CMAKE_INCLUDE_DIRECTORIES_BEFORE, as most
developers will likely assume that the default behavior of directories being
appended will apply. The SYSTEM keyword has the same effect as for the
target_include_directories() command.
提供的路径include_directories()可以是相对路径或绝对路径。相对路径会自动转换为绝对路径,并被视为相对于当前源目录。路径还可以包含生成器表达式。
The paths provided to include_directories() can be relative or absolute.
Relative paths are converted to absolute paths automatically and are treated as
relative to the current source directory. Paths may also contain generator
expressions.
实际操作的细节include_directories()比上面简单的解释更复杂。首先,调用有两个主要作用include_directories():
The details of what include_directories() actually does is more complex than
the simplistic explanation above. Primarily, there are two main effects of
calling include_directories():
INCLUDE_DIRECTORIES当前CMakeLists.txt文件的目录属性中。这意味着在当前目录及以下目录中创建的所有目标都将把目录添加到其
INCLUDE_DIRECTORIES目标属性中。
INCLUDE_DIRECTORIES directory property of
the current CMakeLists.txt file. This means all targets created in the
current directory and below will have the directories added to their
INCLUDE_DIRECTORIES target property.
CMakeLists.txt文件(或更准确地说,当前目录范围)中创建的任何目标也会将路径添加到其
INCLUDE_DIRECTORIES目标属性中,即使这些目标是在调用include_directories(). 这严格仅适用于在当前CMakeLists.txt文件或通过 拉入的其他文件中创建的目标include(),而不适用于在父目录或子目录范围中创建的任何目标。
CMakeLists.txt file (or more accurately,
the current directory scope) will also have the paths added to their
INCLUDE_DIRECTORIES target property, even if those targets were created
before the call to include_directories(). This applies strictly only to the
targets created in the current CMakeLists.txt file or other files pulled in
via include(), but not to any targets created in parent or child directory
scopes.
上述第二点往往让许多开发人员感到惊讶。为了避免造成可能导致此类混乱的情况,如果
include_directories()必须使用该命令,最好
CMakeLists.txt在创建任何目标或使用 或 拉入任何子目录之前include()在文件中尽早调用它add_subdirectory()。
It is the second of the above points which tends to surprise many developers.
To avoid creating situations which may lead to such confusion, if the
include_directories() command must be used, prefer to call it early in a
CMakeLists.txt file before any targets have been created or any
subdirectories have been pulled in with include() or add_subdirectory().
add_definitions(-DSomeSymbol /DFoo=Value ...)
remove_definitions(-DSomeSymbol /DFoo=Value ...)add_definitions(-DSomeSymbol /DFoo=Value ...)
remove_definitions(-DSomeSymbol /DFoo=Value ...)
add_definitions()和
命令remove_definitions()添加和删除COMPILE_DEFINITIONS目录属性中的条目。每个条目应以-D或开头/D,这是绝大多数编译器使用的两种最流行的标志格式。在将定义存储到目录属性之前,CMake 会删除此标志前缀COMPILE_DEFINITIONS,因此无论使用哪个前缀,无论构建项目的编译器或平台如何,都无关紧要。
The add_definitions() and remove_definitions() commands add and remove
entries in the COMPILE_DEFINITIONS directory property. Each entry should
begin with either -D or /D, the two most prevalent flag formats used by the
vast majority of compilers. This flag prefix is stripped off by CMake before
the define is stored in the COMPILE_DEFINITIONS directory property, so it
doesn’t matter which prefix is used, regardless of the compiler or platform on
which the project is built.
正如 一样include_directories(),这两个命令会影响当前CMakeLists.txt文件中创建的所有目标,甚至是在
add_definitions()或remove_definitions()调用之前创建的目标。在子目录范围中创建的目标仅在调用后创建时才会受到影响。COMPILE_DEFINITIONS这是CMake 如何使用目录属性的直接结果。
Just as for include_directories(), these two commands affect all targets
created in the current CMakeLists.txt file, even those created before the
add_definitions() or remove_definitions() call. Targets created in child
directory scopes will only be affected if created after the call. This is a
direct consequence of how the COMPILE_DEFINITIONS directory property is used
by CMake.
尽管不推荐,但也可以使用这些命令指定除定义之外的编译器标志。如果 CMake 无法将特定项目识别为类似于编译器定义,则该项目将不加修改地添加到COMPILE_OPTIONS目录属性中。这种行为的存在是由于历史原因,但新项目应该避免这种行为(请参阅add_compile_options()下面的命令以获取替代方案)。
Although not recommended, it is also possible to specify compiler flags other
than definitions with these commands. If CMake does not recognize a particular
item as looking like a compiler define, that item will instead be added
unmodified to the COMPILE_OPTIONS directory property. This behavior is
present for historical reasons, but new projects should avoid this behavior
(see the add_compile_options() command a little further below for an
alternative).
由于底层目录属性支持生成器表达式,因此这两个命令也支持生成器表达式,但有一些注意事项。生成器表达式只能用于定义的值部分,而不应用于名称部分(即仅在项目中的“=”之后-DVAR=VALUE或根本不用于项目-DVAR)。这与 CMake 如何解析每个项目以检查它是否是编译器定义有关。另请注意,这些命令仅修改目录属性,不会影响COMPILE_DEFINITIONS目标属性。
Since the underlying directory properties support generator expressions, so do
these two commands, with some caveats. Generator expressions should only be
used for the value part of a definition, not for the name part (i.e. only after
the "=" in a -DVAR=VALUE item or not at all for a -DVAR item). This relates
to how CMake parses each item to check if it is a compiler definition or not.
Note also that these commands only modify directory properties, they do not
affect the COMPILE_DEFINITIONS target property.
该add_definitions()命令有许多缺点。为每个项目添加前缀-D或/D将其视为定义的要求与其他 CMake 行为不一致。考虑到命令的名称,省略前缀会使命令将该项视为通用选项,这一事实也是违反直觉的。此外,对生成器表达式仅支持VALUE定义部分的
限制KEY=VALUE也是前缀要求的直接结果。认识到这一点,CMake 3.12 引入了该add_compile_definitions()
命令来替代add_definitions():
The add_definitions() command has a number of shortcomings. The requirement
to prefix each item with -D or /D to have it treated as a definition is not
consistent with other CMake behavior. The fact that omitting the prefix makes
the command treat the item as a generic option instead is also
counter-intuitive given the command’s name. Furthermore, the restriction on
generator expressions only being supported for the VALUE part of a
KEY=VALUE definition is also a direct consequence of the prefix requirement.
In recognition of this, CMake 3.12 introduced the add_compile_definitions()
command as a replacement for add_definitions():
add_compile_definitions(SomeSymbol Foo=Value ...)add_compile_definitions(SomeSymbol Foo=Value ...)
新命令仅处理编译定义,它不需要每个项目上有任何前缀,并且可以使用生成器表达式而无需
VALUE-only 限制。新命令的名称和定义项的处理与类似命令一致target_compile_definitions()
。add_compile_definitions()仍然会影响在同一目录范围内创建的所有目标,无论这些目标是在add_compile_definitions()调用之前还是之后创建,因为这是COMPILE_DEFINITIONS命令操作的基础目录属性的特征,而不是命令本身的特征。
The new command handles only compile definitions, it does not require any
prefix on each item and generator expressions can be used without the
VALUE-only restriction. The new command’s name and treatment of the
definition items is consistent with the analogous target_compile_definitions()
command. add_compile_definitions() still affects all targets created in the
same directory scope regardless of whether those targets are created before or
after add_compile_definitions() is called, as this is a characteristic of the
underlying COMPILE_DEFINITIONS directory property the command manipulates,
not of the command itself.
add_compile_options(opt1 [opt2 ...])add_compile_options(opt1 [opt2 ...])
该add_compile_options()命令用于提供任意编译器选项。与include_directories()、add_definitions()、
remove_definitions()和add_compile_definitions()命令不同,它的行为非常简单且可预测。给定的每个选项
add_compile_options()都会添加到COMPILE_OPTIONS目录属性中。随后在当前目录范围及以下目录范围中创建的每个目标都将在其自己的COMPILE_OPTIONS目标属性中继承这些选项。调用之前创建的任何目标都不会受到影响。与其他目录属性命令相比,此行为更接近开发人员的直观期望。此外,底层目录和目标属性支持生成器表达式,因此该add_compile_options()
命令也支持它们。
The add_compile_options() command is used to provide arbitrary compiler
options. Unlike the include_directories(), add_definitions(),
remove_definitions() and add_compile_definitions() commands, its behavior
is very straightforward and predictable. Each option given to
add_compile_options() is added to the COMPILE_OPTIONS directory property.
Every target subsequently created in the current directory scope and below will
then inherit those options in their own COMPILE_OPTIONS target property. Any
targets created before the call are not affected. This behavior is much closer
to what developers would intuitively expect compared to the other directory
property commands. Furthermore, generator expressions are supported by the
underlying directory and target properties, so the add_compile_options()
command also supports them.
link_libraries(item1 [item2 ...]
[ [debug | optimized | general] item] ...
)
link_directories( [ BEFORE | AFTER ] dir1 [dir2 ...])link_libraries(item1 [item2 ...]
[ [debug | optimized | general] item] ...
)
link_directories( [ BEFORE | AFTER ] dir1 [dir2 ...])
在早期的 CMake 版本中,这两个命令是告诉 CMake 将库链接到其他目标的主要方式。它们会影响调用命令后在当前目录范围及以下目录中创建的所有目标,但任何现有目标不受影响(即类似于 的行为
add_compile_options())。命令中指定的项目link_libraries()可以是 CMake 目标、库名称、库的完整路径甚至链接器标志。
In early CMake versions, these two commands were the primary way to tell CMake
to link libraries into other targets. They affect all targets created in the
current directory scope and below after the commands are called, but any
existing targets remain unaffected (i.e. similar to the behavior of
add_compile_options()). The items specified in the link_libraries() command
can be CMake targets, library names, full paths to libraries or even linker
flags.
Debug宽松地说,可以通过在项目前面添加关键字来使项目仅应用于构建类型debug,或者通过Debug
在项目前面添加关键字 来使其应用于所有构建类型optimized。一个项目前面可以有关键字来general指示它适用于所有构建类型,但由于
general无论如何都是默认的,所以这样做没有什么好处。所有三个关键字仅影响其后的单个项目,而不影响下一个关键字之前的所有项目。强烈建议不要使用这些关键字,因为生成器表达式可以更好地控制何时应添加项目。为了考虑自定义构建类型,debug
如果构建类型在DEBUG_CONFIGURATIONS全局属性中列出,则将其视为配置。
Loosely speaking, an item can be made to apply to just the Debug build type
by preceding it with the keyword debug, or to all build types except Debug
by preceding it with the keyword optimized. An item can be preceded by the
keyword general to indicate that it applies to all build types, but since
general is the default anyway, there is little benefit to doing so. All three
keywords only affect the single item following it, not all items up to the next
keyword. The use of these keywords is strongly discouraged, since generator
expressions provide much better control over when an item should be added. To
account for custom build types, a build type is considered to be a debug
configuration if it is listed in the DEBUG_CONFIGURATIONS global
property.
仅当为 CMake 提供了要链接的裸库名称时,添加的目录link_directories()才有效。CMake 将提供的路径添加到链接器命令行,并让链接器自行查找此类库。给定的目录应该是绝对路径,尽管在 CMake 3.13 之前允许使用相对路径(请参阅CMP0081控制在遇到相对路径时 CMake 是否因错误而停止的策略)。和关键字是在 CMake 3.13 中添加的,具有与 类似的效果BEFORE,包括与两个关键字都不存在时等效的默认行为。AFTERinclude_directories()AFTER
The directories added by link_directories() only have an effect when CMake is
given a bare library name to link to.
CMake adds the supplied paths to the linker command line and leaves the linker
to find such libraries on its own.
The directories given should be absolute paths, although relative paths were
permitted prior to CMake 3.13 (see policy CMP0081 which controls whether
CMake halts with an error if a relative path is encountered).
The BEFORE and AFTER keywords were added in CMake 3.13 and have a similar
effect as they do for include_directories(), including the default behavior
being equivalent to AFTER if neither keyword is present.
出于稳健性原因,在使用 时link_libraries(),更愿意提供完整路径或 CMake 目标的名称。对于这两种情况都不需要链接器搜索目录,并且库的确切位置将提供给链接器。此外,一旦添加了链接器搜索目录
link_directories(),项目就没有方便的方法来删除该搜索路径(如果需要)。通常应避免添加链接器搜索目录,并且通常没有必要。
For robustness reasons, when using link_libraries(), prefer to provide a full
path or the name of a CMake target.
No linker search directory is necessary for either of those cases and the exact
location of the library will be given to the linker.
Furthermore, once a linker search directory has been added by
link_directories(), projects have no convenient way to remove that search
path if they need to.
Adding linker search directories should generally be avoided and is usually not
necessary.
CMake 3.13 也引入了该add_link_options()命令。它类似于命令target_link_options(),作用于目录属性而不是目标属性。
CMake 3.13 also introduced the add_link_options() command.
It is analogous to the target_link_options() command, acting instead on a
directory property rather than on target properties.
add_link_options(item1 [item2...])add_link_options(item1 [item2...])
此命令将项目附加到LINK_OPTIONS目录属性,该属性用于初始化随后在当前目录范围及以下目录范围内创建的所有目标的同名目标属性。与其他目录级命令一样,add_link_options()通常应避免使用目标级命令。
This command appends items to the LINK_OPTIONS directory property, which is
used to initialize the same-named target property of all targets subsequently
created in the current directory scope and below.
As with other directory level commands, add_link_options() should generally
be avoided in favor of the target level command.
当 CMake 构造最终的编译器和链接器命令行时,它会对命令行选项执行重复数据删除步骤。这可以大大减少命令行的长度,这对于实现和试图理解最终使用的命令行选项集的开发人员都有好处。但在某些情况下,重复数据删除是不可取的。例如,一个选项可能需要使用不同的第二个参数重复,例如使用 Clang 传递多个链接器选项:
When CMake constructs the final compiler and linker command lines, it performs a de-duplication step on the command line options. This can greatly reduce the length of command lines, which has benefits for both the implementation and for developers trying to understand the final set of command line options used. In some cases though, de-duplication will be undesirable. For example, an option might need to be repeated with different second arguments, such as passing multiple linker options with Clang:
# This won't work as expected
target_link_options(SomeTarget PRIVATE
-Xlinker -z
-Xlinker defs
)# This won't work as expected
target_link_options(SomeTarget PRIVATE
-Xlinker -z
-Xlinker defs
)
重复数据删除后,第二个-Xlinker将被删除,从而导致命令行选项集不正确-Xlinker -z defs。编译器也存在类似的情况:
After de-duplication, the second -Xlinker would be removed, resulting in the
incorrect set of command line options -Xlinker -z defs.
A similar case exists for the compiler:
# This won't work as expected either
target_compile_options(SomeTarget PRIVATE
-Xassembler --keep
-Xassembler --no_esc
)# This won't work as expected either
target_compile_options(SomeTarget PRIVATE
-Xassembler --keep
-Xassembler --no_esc
)
CMake 提供SHELL:前缀作为防止重复数据删除分割选项组的一种方式。从 CMake 3.12 开始支持编译器选项,从 3.13 开始支持链接器选项。要强制将两个或多个选项视为不应拆分的组,它们应以 为前缀SHELL:并作为单引号字符串给出,选项之间以空格分隔。
CMake provides the SHELL: prefix as a way of preventing groups of options
from being split up by de-duplication.
It is supported since CMake 3.12 for compiler options and 3.13 for linker
options.
To force two or more options to be treated as a group that should not be
split, they should be prefixed by SHELL: and given as a single quoted string
with options separated by spaces.
target_link_options(SomeTarget PRIVATE
"SHELL:-Xlinker -z"
"SHELL:-Xlinker defs"
)
target_compile_options(SomeTarget PRIVATE
"SHELL:-Xassembler --keep"
"SHELL:-Xassembler --no_esc"
)target_link_options(SomeTarget PRIVATE
"SHELL:-Xlinker -z"
"SHELL:-Xlinker defs"
)
target_compile_options(SomeTarget PRIVATE
"SHELL:-Xassembler --keep"
"SHELL:-Xassembler --no_esc"
)
重复选项的存在并不总是显而易见的。有时它们是由 CMake 生成的,作为翻译项目指定内容的一部分。请考虑以下更通用的方式来添加上述链接器选项,该方式适用于 Clang 和 gcc:
The presence of duplicated options isn’t always obvious. Sometimes they are generated by CMake as part of translating what the project specifies. Consider the following more generic way of adding the above linker options, which is intended to work for both Clang and gcc:
# This won't work as expected for some compiler front ends
target_link_options(SomeTarget PRIVATE
"LINKER:-z,defs"
)# This won't work as expected for some compiler front ends
target_link_options(SomeTarget PRIVATE
"LINKER:-z,defs"
)
使用 Clang 时,LINKER:-z,defs表达式会扩展为与上例中-Xlinker出现两次相同的链接器选项。为了防止重复数据删除-Xlinker在这种情况下删除第二个选项,LINKER:需要SHELL:结合使用:
When using Clang, the LINKER:-z,defs expression expands to the same linker
options as in the above example where -Xlinker appears twice.
To prevent de-duplication from removing the second -Xlinker option in this
situation, LINKER: and SHELL: need to be combined:
target_link_options(SomeTarget PRIVATE
"LINKER:SHELL:-z defs"
)target_link_options(SomeTarget PRIVATE
"LINKER:SHELL:-z defs"
)
、SHELL:和LINKER:前缀LINKER:SHELL:在目标属性级别进行处理。这意味着它们可以与任何操作目标属性的命令一起使用。
LINK_OPTIONS并INTERFACE_LINK_OPTIONS支持所有前缀。
COMPILE_OPTIONS,INTERFACE_COMPILE_OPTIONS也STATIC_LIBRARY_OPTIONS
只有支持SHELL:。由于所有这些目标属性都是从同名的目录属性初始化的,因此这些目录属性也可以使用前缀。
The SHELL:, LINKER: and LINKER:SHELL: prefixes are handled at the target
property level.
This means they can be used with any of the commands that manipulate target
properties.
LINK_OPTIONS and INTERFACE_LINK_OPTIONS support all of the prefixes.
COMPILE_OPTIONS, INTERFACE_COMPILE_OPTIONS and STATIC_LIBRARY_OPTIONS
only support SHELL:.
Since all of these target properties are initialized from directory properties
of the same names, those directory properties can also use the prefixes.
属性是项目应该寻求影响编译器和链接器标志的主要方式。最终用户无法直接操作属性,因此项目完全控制属性的设置方式。然而,在某些情况下,用户可能希望添加自己的编译器或链接器标志。他们可能希望添加更多警告选项,打开特殊的编译器功能,例如清理程序或调试开关等。对于这些情况,变量更合适。
Properties are the main way that projects should seek to influence compiler and linker flags. End users cannot manipulate properties directly, so the project is in full control of how the properties are set. There are situations, however, where the user will want to add their own compiler or linker flags. They may wish to add more warning options, turn on special compiler features such as sanitizers or debugging switches, and so on. For these situations, variables are more appropriate.
CMake 提供了一组变量,用于指定要与各种目录、目标和源文件属性提供的编译器和链接器标志合并。它们通常是缓存变量,以允许用户轻松查看和修改它们,但它们也可以在项目CMakeLists.txt文件中设置为常规 CMake 变量(项目应该避免这种情况)。CMake 第一次在构建目录中运行时为缓存变量提供合适的默认值。
CMake provides a set of variables that specify compiler and linker flags to be
merged with those provided by the various directory, target and source file
properties. They are normally cache variables to allow the user to easily view
and modify them, but they can also be set as regular CMake variables within the
project’s CMakeLists.txt files (something projects should aim to avoid).
CMake gives the cache variables suitable default values the first time it runs
in a build directory.
直接影响编译器标志的主要变量具有以下形式:
The primary variables directly affecting the compiler flags have the following form:
CMAKE_<LANG>_FLAGS
CMAKE_<LANG>_FLAGS
CMAKE_<LANG>_FLAGS_<CONFIG>
CMAKE_<LANG>_FLAGS_<CONFIG>
<LANG>对应于正在编译的语言,典型值为
C、CXX、Fortran等Swift。该部分是与其中一种构建类型相对应的<CONFIG>大写字符串,例如DEBUG、RELEASE或。第一个变量将应用于所有构建类型,包括带有空. 第二个变量仅适用于 指定的构建类型。因此,使用调试配置构建的 C++ 文件将具有来自 和 的编译器标志。RELWITHDEBINFOMINSIZERELCMAKE_BUILD_TYPE<CONFIG>CMAKE_CXX_FLAGSCMAKE_CXX_FLAGS_DEBUG
<LANG> corresponds to the language being compiled, with typical values being
C, CXX, Fortran, Swift and so on.
The <CONFIG> part is an uppercase string corresponding to one of the build
types, such as DEBUG, RELEASE, RELWITHDEBINFO or MINSIZEREL.
The first variable will be applied to all build types, including single
configuration generators with an empty CMAKE_BUILD_TYPE.
The second variable is only applied to the build type specified by <CONFIG>.
Thus, a C++ file being built with a Debug configuration would have compiler
flags from both CMAKE_CXX_FLAGS and CMAKE_CXX_FLAGS_DEBUG.
遇到的第一个project()命令将为这些变量创建缓存变量(如果它们尚不存在的话)(这有点简化,更完整的解释在第 22 章工具链和交叉编译中给出)。因此,第一次运行 CMake 后,可以轻松在 CMake GUI 应用程序中检查它们的值。例如,对于一个特定的编译器,默认情况下定义了 C++ 语言的以下变量:
The first project() command encountered will create cache variables for these
if they don’t already exist (this is a bit of a simplification, a more complete
explanation is given in Chapter 22, Toolchains And Cross Compiling). Therefore, after
the first time CMake has been run, their values are easy to check in the CMake
GUI application. As an example, for one particular compiler, the following
variables for the C++ language are defined by default:
|
|
|
|
|
|
|
|
|
|
链接器标志的处理类似。它们由以下变量系列控制:
The handling of linker flags is similar. They are controlled by the following family of variables:
CMAKE_<TARGETTYPE>_LINKER_FLAGS
CMAKE_<TARGETTYPE>_LINKER_FLAGS
CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG>
CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG>
这些特定于特定类型的目标,每个目标都在第 4 章“构建简单目标”中介绍过。变量名称的部分<TARGETTYPE>必须是以下之一:
These are specific to a particular type of target, each of which was introduced
back in Chapter 4, Building Simple Targets. The <TARGETTYPE> part of the variable
name must be one of the following:
EXE
EXE
add_executable()。
add_executable().
SHARED
SHARED
add_library(name SHARED …)或等效创建的目标,例如省略SHARED关键字但将BUILD_SHARED_LIBS变量设置为 true。
add_library(name SHARED …) or equivalent,
such as omitting the SHARED keyword but with the BUILD_SHARED_LIBS variable
set to true.
STATIC
STATIC
add_library(name STATIC …)或等效创建的目标,例如省略STATIC关键字但将BUILD_SHARED_LIBS变量设置为 false 或未定义。
add_library(name STATIC …) or equivalent,
such as omitting the STATIC keyword but with the BUILD_SHARED_LIBS variable
set to false or not defined.
MODULE
MODULE
add_library(name MODULE …)。
add_library(name MODULE …).
就像编译器标志一样,CMAKE_<TARGETTYPE>_LINKER_FLAGS在链接任何构建配置时使用它们,而
CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG>仅为相应的CONFIG. 在某些平台上,部分或全部链接器标志为空字符串的情况并不罕见。
Just like for the compiler flags, the CMAKE_<TARGETTYPE>_LINKER_FLAGS are
used when linking any build configuration, whereas the
CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG> flags are only added for the
corresponding CONFIG. It is not unusual for some or all of the linker flags
to be empty strings on some platforms.
CMake 教程和示例代码经常使用上述变量来控制编译器和链接器标志。这在 CMake 3.0 之前的时代是相当常见的做法,但随着 CMake 3.0 及更高版本的重点转向以目标为中心的模型,此类示例不再是一个值得遵循的好模型。它们通常会导致许多非常常见的错误,下面列出了一些更常见的错误。
CMake tutorials and example code frequently use the above variables to control the compiler and linker flags. This was fairly common practice in the pre CMake 3.0 era, but with the focus shifting to a target-centric model with CMake 3.0 and later, such examples are no longer a good model to follow. They often lead to a number of very common mistakes, with some of the more prevalent ones presented below.
如果需要设置多个编译器标志,则需要将它们指定为单个字符串,而不是列表。如果标志变量的内容包含分号,CMake 将无法正确处理标志变量,如果项目指定了分号,则列表将变成这样。
If multiple compiler flags need to be set, they need to be specified as a single string, not as a list. CMake will not properly handle flag variables if their contents contain semicolons, which is what a list would be turned into if specified by the project.
# Wrong, list used instead of a string
set(CMAKE_CXX_FLAGS -Wall -Werror)
# Correct, but see later sections for why appending
# would be preferred
set(CMAKE_CXX_FLAGS "-Wall -Werror")
# Appending to existing flags the correct way
# (two methods)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
string(APPEND CMAKE_CXX_FLAGS " -Wall -Werror")# Wrong, list used instead of a string
set(CMAKE_CXX_FLAGS -Wall -Werror)
# Correct, but see later sections for why appending
# would be preferred
set(CMAKE_CXX_FLAGS "-Wall -Werror")
# Appending to existing flags the correct way
# (two methods)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
string(APPEND CMAKE_CXX_FLAGS " -Wall -Werror")
上面提到的所有变量都是缓存变量。可以定义同名的非缓存变量,它们将覆盖当前目录范围及其子目录(即由
add_subdirectory())创建的缓存变量。但是,当项目尝试强制更新缓存变量而不是局部变量时,可能会出现问题。类似下面的代码往往会使项目更难使用,并且当开发人员想要通过 CMake GUI 应用程序或类似应用程序更改自己的构建的标志时,可能会导致开发人员感觉自己正在与项目作斗争:
All of the variables mentioned above are cache variables. Non-cache variables
of the same name can be defined and they will override the cache variables for
the current directory scope and its children (i.e. those created by
add_subdirectory()). Problems can arise, however, when a project tries to
force updating the cache variable instead of a local variable. Code like the
following tends to make projects harder to work with and can lead to developers
feeling like they are fighting the project when they want to change flags for
their own build through the CMake GUI application or similar:
# Case 1: Only has an effect if the variable isn't
# already in the cache
set(CMAKE_CXX_FLAGS "-Wall -Werror"
CACHE STRING "C++ flags"
)
# Case 2: Using FORCE to always update the cache
# variable, but this overwrites any changes
# a developer might make to the cache
set(CMAKE_CXX_FLAGS "-Wall -Werror"
CACHE STRING "C++ flags" FORCE
)
# Case 3: FORCE + append = recipe for disaster
# (see discussion below)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror"
CACHE STRING "C++ flags" FORCE
)# Case 1: Only has an effect if the variable isn't
# already in the cache
set(CMAKE_CXX_FLAGS "-Wall -Werror"
CACHE STRING "C++ flags"
)
# Case 2: Using FORCE to always update the cache
# variable, but this overwrites any changes
# a developer might make to the cache
set(CMAKE_CXX_FLAGS "-Wall -Werror"
CACHE STRING "C++ flags" FORCE
)
# Case 3: FORCE + append = recipe for disaster
# (see discussion below)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror"
CACHE STRING "C++ flags" FORCE
)
上面的第一个案例凸显了 CMake 新手开发人员所犯的常见疏忽。如果没有FORCE关键字,该set()命令仅更新尚未定义的缓存变量。因此,CMake 的第一次运行可能会按照开发人员的意图执行(如果放置在任何project()
命令之前),但如果更改该行以指定其他标志,则该更改将不会应用于现有构建,因为此时变量已经在缓存中。
The first case above highlights a common oversight made by developers new to
CMake. Without the FORCE keyword, the set() command only updates a cache
variable if it is not already defined. The first run of CMake may therefore
appear to do what the developer intended (if placed before any project()
command), but if the line is ever changed to specify something else for the
flags, that change won’t be applied to an existing build because the variable
will already be in the cache at that point.
发现这一点的通常反应是使用它来FORCE确保缓存变量始终更新,如第二种情况所示,但这会产生另一个问题。缓存是开发人员在本地更改变量而无需编辑项目文件的主要手段。如果项目FORCE以这种方式单方面设置缓存变量,则开发人员对该缓存变量所做的任何更改都将丢失。
The usual reaction to discovering this is to then use FORCE to ensure the
cache variable is always updated, as shown in the second case, but this then
creates another problem.
The cache is a primary means for developers to change variables locally
without having to edit project files.
If a project uses FORCE to unilaterally set cache variables in this manner,
any change made by the developer to that cache variable will be lost.
第三种情况问题更大,因为每次运行 CMake 时,都会再次附加标志,导致标志集不断增长和重复。使用FORCE像这样更新编译器和链接器标志的缓存很少是一个好主意。
The third case is even more problematic because every time CMake is run, the
flags will be appended again, leading to an ever growing and repeating set of
flags.
Using FORCE to update the cache like this for compiler and linker flags is
rarely a good idea.
正确的行为不是简单地删除FORCE关键字,而是设置非缓存变量而不是缓存变量。然后可以安全地将标志附加到当前值,因为缓存变量保持不变。每次 CMake 运行都将从缓存变量中的同一组标志开始,无论调用 CMake 的频率如何。开发人员选择对缓存变量进行的任何更改也将被保留。
Rather than simply removing the FORCE keyword, the correct behavior is to
set a non-cache variable rather than the cache variable. It is then safe to
append flags to the current value because the cache variable is left untouched.
Every CMake run will start with the same set of flags from the cache variable,
regardless of how often CMake is invoked.
Any changes the developer chooses to make to the cache variable will also be
preserved.
# Preserves the cache variable contents, appends new
# flags safely
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")# Preserves the cache variable contents, appends new
# flags safely
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
如上所述,开发人员有时会试图在其CMakeLists.txt文件中单方面设置编译器标志,如下所示:
As touched on above, developers are sometimes tempted to unilaterally set
compiler flags in their CMakeLists.txt files like so:
# Not ideal, discards any developer settings from cache
set(CMAKE_CXX_FLAGS "-Wall -Werror")# Not ideal, discards any developer settings from cache
set(CMAKE_CXX_FLAGS "-Wall -Werror")
因为这会丢弃缓存变量设置的任何值,所以开发人员失去了轻松注入自己的标志的能力。这迫使开发人员在整个项目中寻找并修改有问题的行。对于具有许多子目录的复杂项目,这可能很乏味。如果可能,项目应该更愿意将标志附加到现有值。
Because this discards any value set by the cache variable, developers lose their ability to easily inject their own flags. This forces developers to go hunting through the project to find and modify the offending lines. For a complex project with many subdirectories, this can be tedious. Where possible, projects should prefer to append flags to the existing value.
本指南的一个合理例外可能是项目需要强制执行一组强制的编译器或链接器标志。CMakeLists.txt
在这种情况下,一个可行的折衷方案可能是尽早
在顶级文件中设置变量值,最好是在cmake_minimum_required()命令之后的最顶层(或者更好,如果正在使用工具链文件,请在工具链文件中设置 - 请参阅第 1 章) 22,工具链和交叉编译了解更多详细信息)。但请记住,随着时间的推移,该项目本身可能会成为另一个项目的子项目,此时它将不再是构建的顶层,并且这种妥协的适用性可能会降低。
One reasonable exception to this guideline may be if a project is required to
enforce a mandated set of compiler or linker flags. In such cases, a workable
compromise may be to set the variable values in the top level CMakeLists.txt
file as early as possible, ideally at the very top just after the
cmake_minimum_required() command (or even better, in the toolchain file if
one is being used - see Chapter 22, Toolchains And Cross Compiling for further
details). Keep in mind though that over time, the project may itself become a
child of another project, at which point it would no longer be the top level of
the build and the suitability of this compromise may be reduced.
编译器和链接器标志变量比较模糊的方面之一是构建过程中实际使用它们的值的点。人们可能合理地期望以下代码的行为如内联注释中所述:
One of the more obscure aspects of the compiler and linker flag variables is the point in the build process at which their value actually gets used. One might reasonably expect the following code to behave as noted in the inline comments:
# Save the original set of flags so we can
# restore them later
set(oldCxxFlags "${CMAKE_CXX_FLAGS}")
# This library has stringent build requirements, so
# enforce them just for it alone.
# WARNING: This doesn't do what it may appear to do!
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
add_library(StrictReq STATIC ...)
# Less strict requirements from here, so restore the
# original set of compiler flags
set(CMAKE_CXX_FLAGS "${oldCxxFlags}")
add_library(RelaxedReq STATIC ...)# Save the original set of flags so we can
# restore them later
set(oldCxxFlags "${CMAKE_CXX_FLAGS}")
# This library has stringent build requirements, so
# enforce them just for it alone.
# WARNING: This doesn't do what it may appear to do!
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
add_library(StrictReq STATIC ...)
# Less strict requirements from here, so restore the
# original set of compiler flags
set(CMAKE_CXX_FLAGS "${oldCxxFlags}")
add_library(RelaxedReq STATIC ...)
令人惊讶的是,按照上述安排,StrictReq
图书馆将不会用旗帜建造-Wall -Werror。直观上,人们可能认为 CMake 使用的是调用时的变量值add_library(),但实际上使用的是该目录范围处理结束时的变量值。换句话说,重要的是变量在CMakeLists.txt该目录的文件末尾保存的值。对于不知情的人来说,这可能会在各种情况下导致意想不到的结果。
It may be surprising to learn that with the arrangement above, the StrictReq
library will not be built with -Wall -Werror flags. Intuitively, one may
expect that the variable’s value at the time of the call to add_library() is
what CMake uses, but in fact it is the variable’s value at the end of
processing for that directory scope that gets used. In other words, what
matters is the value the variable holds at the end of the CMakeLists.txt file
for that directory. This can lead to unexpected results in a variety of
situations for the unaware.
开发人员遇到这种行为的主要方法之一是将编译器和链接器变量视为立即应用于创建的任何目标。另一个相关的陷阱是在include()创建目标之后使用 an 并且包含的文件修改编译器或链接器变量。这还将更改当前目录范围中已定义目标的编译器和链接器标志。编译器和链接器变量的这种延迟性质使得它们难以使用。理想情况下,项目只会在顶层
CMakeLists.txt文件的早期修改它们(如果有的话),以便最大限度地减少误用和开发人员意外的机会。
One of the main ways developers get caught out by this behavior is to treat the
compiler and linker variables as though they apply immediately to any targets
that are created. Another related trap is when an include() is used after
targets have been created and the included file(s) modify the compiler or
linker variables. This would also alter the compiler and linker flags for the
already defined targets in the current directory scope.
This delayed nature of compiler and linker variables makes them fragile to
work with.
Ideally, a project would only modify them early in the top level
CMakeLists.txt file, if at all, so as to minimize opportunities for misuse
and developer surprise.
在设置仅适用于特定语言的编译器标志时,需要注意一些限制。可以使用生成器表达式为特定目标设置特定于语言的编译器标志,如以下示例所示(为简单起见,假设编译器支持该-fno-exceptions选项):
There are limitations to be aware of when it comes to setting compiler flags
that should only apply for specific languages.
It is possible to set language-specific compiler flags for a particular target
using generator expressions, as the following example shows (for simplicity,
the compiler is assumed to support the -fno-exceptions option):
target_compile_options(Foo PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
)target_compile_options(Foo PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
)
不幸的是,这对于 Visual Studio 或 Xcode 来说不会按预期工作。这些生成器的实现不支持在目标级别为不同语言设置不同的标志。相反,如果目标有任何 C++ 源,它们会使用假定为 C++ 的目标语言来计算生成器表达式,否则为 C。这不仅适用于编译选项,还适用于编译定义和包含目录。此限制是为了避免显着降低构建性能而需要进行折衷的结果。如果项目愿意接受较慢的构建,则可以使用源文件属性来应用编译器标志。例如:
Unfortunately, this will not work as expected for Visual Studio or for Xcode. Those generators’ implementations do not support setting different flags for different languages at the target level. Instead, they evaluate generator expressions with the target language assumed to be C++ if the target has any C++ sources, or as C otherwise. This applies not just to compile options, but also to compile definitions and include directories. This limitation is a result of a compromise needed to avoid considerably degrading the build performance. If projects are willing to accept slower builds, compiler flags can be applied using source file properties instead. For example:
add_executable(Foo src1.c src2.cpp)
set_property(SOURCE src2.cpp APPEND PROPERTY
COMPILE_OPTIONS -fno-exceptions
)add_executable(Foo src1.c src2.cpp)
set_property(SOURCE src2.cpp APPEND PROPERTY
COMPILE_OPTIONS -fno-exceptions
)
源文件属性也有其自身的限制,如第 9.5 节“源属性”中所述
。特别要注意的是,Xcode 生成器有限制,无法支持特定于配置的源文件属性,因此$<CONFIG>需要避免使用生成器表达式。
Source file properties also have their own limitations, as discussed back in
Section 9.5, “Source Properties”.
In particular, note that the Xcode generator has restrictions which prevent it
from supporting configuration-specific source file properties, so generator
expressions like $<CONFIG> would need to be avoided.
解决这些限制的更好方法是将不同的语言拆分到它们自己的单独目标中,而不是将它们组合到同一目标中。然后,编译器标志可以应用于整个目标,这将适用于所有 CMake 生成器,并且不会降低构建性能。
A better way to work around these limitations is to split out the different languages to their own separate targets rather than combining them in the same target. The compiler flags can then be applied to the whole target, which will work for all CMake generators and will not degrade build performance.
add_library(Foo_c src1.c)
add_executable(Foo src2.cpp)
target_link_libraries(Foo PRIVATE Foo_c)
target_compile_options(Foo PRIVATE -fno-exceptions)add_library(Foo_c src1.c)
add_executable(Foo src2.cpp)
target_link_libraries(Foo PRIVATE Foo_c)
target_compile_options(Foo PRIVATE -fno-exceptions)
一个不太理想的解决方法是使用CMAKE_<LANG>_FLAGS由 Visual Studio 和 Xcode 正确处理的变量,但它们将不加区别地应用于目录范围内的所有目标,并且最好单独留给开发人员操作。
A less ideal workaround is to use the CMAKE_<LANG>_FLAGS variables which
are handled correctly by Visual Studio and Xcode, but they will apply
indiscriminately to all targets in a directory scope and should preferably be
left alone for developers to manipulate.
本章涵盖了自早期版本以来 CMake 经历了一些最重大改进的领域。读者应该期望在网上和其他地方遇到大量示例和教程,它们仍然推荐使用使用变量和目录属性命令的旧方法的模式和方法。应该理解的是,
target_…()命令应该是 CMake 3.0+ 时代的首选方式。
This chapter has covered areas of CMake which have undergone some of the most
significant improvements since earlier versions. The reader should expect to
encounter plenty of examples and tutorials online and elsewhere which still
recommend patterns and approaches employing the older methods using variables
and directory property commands. It should be understood that the
target_…() commands should be the preferred approach in the CMake 3.0+ era.
项目应寻求使用
target_link_libraries()命令定义目标之间的所有依赖关系。这清楚地表达了目标之间关系的性质,并向所有项目开发人员明确传达了目标之间的关系。该target_link_libraries()
命令应该优先于link_libraries()直接操作目标或目录属性。类似地,与变量、目录属性命令或直接操作属性相比,其他target_…()命令提供了更干净、更一致和更健壮的方式来操作编译器和链接器标志。
Projects should seek to define all dependencies between targets with the
target_link_libraries() command. This clearly expresses the nature of the
relationships between targets and communicates unambiguously to all of a
project’s developers how targets are related. The target_link_libraries()
command should be preferred over link_libraries() or manipulating target or
directory properties directly. Similarly, the other target_…() commands
offer a cleaner, more consistent and more robust way to manipulate compiler and
linker flags than variables, directory property commands or direct manipulation
of properties.
CMake 3.13 引入了许多与链接器选项相关的新命令和属性,其中一些是出于一致性原因或为了解决特定用例而添加的。项目通常应避免使用 newadd_link_options()目录级别命令,而更喜欢使用 newtarget_link_options()命令。CMake 3.13还引入了新的目标级别命令
target_link_directories(),它是对现有目录级别命令的补充link_directories()。出于稳健性原因,应避免使用这两个链接目录命令。建议项目链接到目标名称或使用库的完整路径,其中这些库不在默认链接器搜索路径上预期的目录中。
CMake 3.13 introduced a number of new commands and properties related to
linker options, some of which were added for consistency reasons or to address
specific use cases.
Projects should generally avoid the new add_link_options() directory level
command and prefer to use the new target_link_options() command instead.
CMake 3.13 also introduced a new target level command
target_link_directories(), which is a complement to the existing directory
level link_directories() command.
Both of these link directory commands should be avoided for robustness reasons.
Projects are advised to link to target names or use full paths to libraries
where those libraries are not in directories expected to be on the default
linker search path.
以下一般指南可能有助于确定在特定情况下哪种方法更合适:
The following general guide may be useful in deciding which methods are more appropriate in given situations:
target_…()命令来描述目标之间的关系以及修改编译器和链接器的行为。
target_…() commands to describe
relationships between targets and to modify compiler and linker behavior.
target_…()命令将建立项目中所有开发人员都可以遵循的清晰模式。如果必须使用目录属性命令,请尽早在CMakeLists.txt文件中执行此操作,以避免前面部分中描述的一些不太直观的行为。
target_…() commands instead will establish clear patterns that all
developers in a project can follow. If directory property commands must be
used, do so as early in the CMakeLists.txt file as possible to avoid some of
the less intuitive behavior described in the preceding sections.
CMAKE_…_FLAGS变量及其配置的特定对应项。考虑将这些保留给可能希望在本地随意更改它们的开发人员。如果需要在整个项目的基础上应用更改,请考虑在项目的顶层使用一些战略目录属性命令,但要考虑是否确实应该单方面应用此类设置。对此的一个部分例外是在工具链文件中,可以定义初始默认值(有关该领域的详细讨论,请参阅第 22 章“工具链和交叉编译”)。
CMAKE_…_FLAGS variables and
their configuration specific counterparts. Consider these to be reserved for
the developer who may wish to change them locally at will. If changes need to
be applied on a whole-of-project basis, consider using a few strategic
directory property commands at the top level of the project instead, but
consider whether such settings really should be unilaterally applied. A partial
exception to this is in toolchain files where initial defaults may be defined
(see Chapter 22, Toolchains And Cross Compiling for a detailed discussion of this
area).
开发人员应该熟悉PRIVATE和PUBLIC关系
的概念INTERFACE。它们是命令集的关键部分target_…()
,对于项目的安装和打包阶段变得更加重要。将其视为PRIVATE目标本身的意义、与
INTERFACE目标相关的事物的意义以及PUBLIC两种行为相结合的意义。虽然将所有内容标记为 可能很诱人
PUBLIC,但这可能会不必要地将依赖项暴露到超出目标所需的范围。构建时间可能会受到影响,并且私有依赖关系可能会被强制到其他不需要了解它们的目标上。这反过来又对其他领域产生了强烈影响,例如符号可见性(在
第 21.5 节“符号可见性”中详细讨论)。因此,最好从依赖项开始,
并且仅在明确链接到目标的那些依赖项需要依赖项时才PRIVATE进行依赖项。PUBLIC
Developers should become familiar with the concepts of PRIVATE, PUBLIC and
INTERFACE relationships. They are a critical part of the target_…()
command set and they become even more important for the install and packaging
stages of a project. Think of PRIVATE as meaning for the target itself,
INTERFACE for things that link against the target and PUBLIC as meaning
both behaviors combined. While it may be tempting to just mark everything as
PUBLIC, this may unnecessarily expose dependencies out beyond targets they
need to. Build times can be impacted and private dependencies can be forced
onto other targets which should not have to know about them. This in turn has a
strong impact on other areas such as symbol visibility (discussed in detail in
Section 21.5, “Symbol Visibility”). Therefore, prefer to start with a dependency as
PRIVATE and only make it PUBLIC when it is clear that the dependency is
needed by those linking to the target.
该INTERFACE关键字往往主要用于导入或接口库目标,或者偶尔用于将缺少的依赖项添加到项目部分中定义的目标,而开发人员可能不允许更改该目标。这样的示例包括为较旧的 CMake 版本编写的项目子部分,因此不使用命令target_…(),或者具有导入目标的外部库,这些目标省略了链接到它们的目标所需的一些重要标志。target_link_libraries()CMake 3.13 删除了无法调用以对不同目录范围中定义的目标进行操作的限制。对于所有其他target_…()命令,以前没有这样的限制,因此它们始终可以用于扩展项目中其他地方定义的目标的接口属性。
第 29.5.1 节“目标源”重新讨论了这个主题,演示了如何使用这些功能来促进更加模块化的项目结构。
The INTERFACE keyword tends to be used mostly for imported or interface
library targets, or occasionally for adding missing dependencies to a target
defined in a part of the project which the developer may not be allowed to
change. Examples of this include sub-parts of the project that were written for
older CMake versions and therefore don’t use the target_…() commands, or
external libraries with imported targets that omit some important flags needed
by targets linking to them.
CMake 3.13 removed the restriction that target_link_libraries() could not be
called to operate on a target defined in a different directory scope.
For all other target_…() commands, there was no such restriction previously
and so they can always be used to extend the interface properties of targets
defined elsewhere in the project.
Section 29.5.1, “Target Sources” revisits this topic, demonstrating how these capabilities
can also be used to promote a more modular project structure.
随着 C 和 C++ 语言的不断发展,开发人员越来越需要了解编译器和链接器标志,以支持其代码使用的 C 和/或 C++ 版本。不同的编译器使用不同的标志,但即使使用相同的编译器和链接器,标志也可用于选择标准库的不同实现。
With the ongoing evolution of the C and C++ languages, developers are increasingly required to understand the compiler and linker flags that enable support for the C and/or C++ version their code uses. Different compilers use different flags, but even when using the same compiler and linker, flags can be used to select different implementations of the standard library.
在 C++11 支持相对较新的时代,CMake 没有直接支持选择使用哪个标准,因此项目只能自行确定所需的标志。在 CMake 3.1 中,引入了一些功能,允许以一致且方便的方式选择 C 和 C++ 标准,从而抽象出各种编译器和链接器差异。这种支持在后续版本中得到了扩展,从 CMake 3.6 开始涵盖了大多数常见编译器(CMake 3.2 添加了大部分编译器支持,3.6 添加了 Intel 编译器)。
In the days where C++11 support was relatively new, CMake had no direct support for choosing which standard to use, so projects were left to work out the required flags on their own. In CMake 3.1, features were introduced to allow the C and C++ standard to be selected in a consistent and convenient way, abstracting away the various compiler and linker differences. This support has been extended in subsequent versions and from CMake 3.6 covers most common compilers (CMake 3.2 added most of the compiler support, 3.6 added the Intel compiler).
CMake 提供了两种主要方法来指定语言要求。第一个是直接设置语言标准,第二个是允许项目指定他们需要的语言功能,并让 CMake 选择合适的语言标准。虽然功能主要由 C 和 C++ 语言驱动,但也支持其他语言和伪语言,例如 CUDA 和 Objective C/C++。
Two main methods are provided by CMake for specifying language requirements. The first is to set the language standard directly and the second is to allow projects to specify the language features they need and let CMake select the appropriate language standard. While the functionality has largely been driven by the C and C++ languages, other languages and pseudo-languages such as CUDA and Objective C/C++ are also supported.
项目控制构建所使用的语言标准的最简单方法是直接设置它们。使用这种方法,开发人员不需要了解或指定代码使用的各个语言功能,他们只需要设置一个数字来指示代码假定支持的标准。这不仅易于理解和使用,而且还有一个优点,即可以相对简单地确保整个项目使用相同的标准。这在链接阶段变得很重要,其中应在所有链接库和目标文件中使用一致的标准库。如果标头根据所使用的语言标准进行不同的定义,那么在编译时也很重要。
The simplest way for a project to control the language standards used by a build is to set them directly. Using this approach, developers do not need to know or specify the individual language features used by the code, they just need to set a single number indicating the standard the code assumes is supported. Not only is this easy to understand and use, it also has the advantage that it is relatively straightforward to ensure that the same standard is used throughout a project. This becomes important at the link stage where a consistent standard library should be used across all the linked libraries and object files. It can also be important at compile time if headers define things differently depending on what language standard is being used.
与 CMake 的常见模式一样,目标属性控制在构建目标源以及链接最终可执行文件或共享库时将使用哪个标准。对于给定的语言,存在与指定标准相关的三个目标属性。在下文中,<LANG>最常见的是C或CXX,但是
CUDA、OBJC和OBJCXX也受到更新的 CMake 版本的支持。
As is the usual pattern with CMake, target properties control which standard
will be used when building that target’s sources and when linking the final
executable or shared library.
For a given language, there are three target properties related to specifying
the standard.
In the following, <LANG> will most commonly be either C or CXX, but
CUDA, OBJC and OBJCXX are also supported with more recent CMake
versions.
<LANG>_STANDARD
<LANG>_STANDARD
C_STANDARD支持值 90、99 和 11,CMake 3.21 添加了 17 和 23。
CXX_STANDARD支持值 98、11 和 14。自 CMake 3.8 以来还支持值 17,自 CMake 3.12 以来还支持 20,自 CMake 3.20 以来还支持 23。
CUDA_STANDARDCXX_STANDARD是通常控制的 CUDA 特定版本。如果CUDA_STANDARD未定义,它实际上会回退到
CXX_STANDARD。支持的值CUDA_STANDARD至少为 98 和 11。自 CMake 3.10 起支持 14,自 CMake 3.18 起支持 03、17 和 20。
OBJC_STANDARD遵循OBJCXX_STANDARD类似的模式。如果未定义它们,则C_STANDARD和CXX_STANDARD分别用作后备。OBJC_STANDARD在实践中,设置或是不常见的OBJCXX_STANDARD,因为通常需要回退到C_STANDARD和CXX_STANDARD来确保 C/C++ 代码使用与 Objective C/C++ 代码一致的语言标准。
人们可以合理地假设,随着其他语言标准随着时间的推移而发展,后续的 CMake 版本将添加对其他语言标准的支持。创建目标时,<LANG>_STANDARD将从变量中获取该属性的初始值CMAKE_<LANG>_STANDARD。
C_STANDARD supports the values 90, 99 and 11, with CMake 3.21 adding 17 and
23.
CXX_STANDARD supports the values 98, 11 and 14. The value 17 is also
supported since CMake 3.8, 20 since CMake 3.12 and 23 since CMake 3.20.
CUDA_STANDARD is a CUDA-specific version of what CXX_STANDARD would
normally control.
If CUDA_STANDARD is not defined, it effectively falls back to
CXX_STANDARD.
Supported values for CUDA_STANDARD are at least 98 and 11.
14 is supported since CMake 3.10, while 03, 17 and 20 are supported since
CMake 3.18.
OBJC_STANDARD and OBJCXX_STANDARD follow a similar
pattern.
If they are not defined, then C_STANDARD and CXX_STANDARD are used as
fallbacks respectively.
In practice, it would be unusual to set OBJC_STANDARD or OBJCXX_STANDARD,
since the fallback to C_STANDARD and CXX_STANDARD would typically be
desirable to ensure that C/C++ code uses language standards consistent with
the Objective C/C++ code.
One would reasonably presume that later CMake versions would add support for
other language standards as they evolve over time.
When a target is created, the initial value of this <LANG>_STANDARD property
is taken from the CMAKE_<LANG>_STANDARD variable.
<LANG>_STANDARD_REQUIRED
<LANG>_STANDARD_REQUIRED
<LANG>_STANDARD属性指定了项目所需的语言标准,<LANG>_STANDARD_REQUIRED但确定该语言标准是被视为最低要求还是仅被视为“如果可用则使用”指南。人们可能直观地认为
<LANG>_STANDARD默认情况下这是一项要求,但无论好坏,这些<LANG>_STANDARD_REQUIRED属性都是OFF默认的。当 时OFF,如果编译器不支持所请求的标准,CMake 会将请求衰减为较早的标准,而不是因错误而停止。这种衰退行为对于新开发人员来说通常是意想不到的,并且在实践中可能会造成混乱。因此,对于大多数项目来说,在指定<LANG>_STANDARD
属性时,其相应的<LANG>_STANDARD_REQUIRED属性几乎总是需要设置为 true,以确保特定请求的标准被视为严格要求。创建目标时,将从
CMAKE_<LANG>_STANDARD_REQUIRED变量中获取该属性的初始值。
<LANG>_STANDARD property specifies
the language standard the project wants, <LANG>_STANDARD_REQUIRED determines
whether that language standard is treated as a minimum requirement or as just a
"use if available" guideline. One might intuitively expect that
<LANG>_STANDARD would be a requirement by default, but for better or worse,
the <LANG>_STANDARD_REQUIRED properties are OFF by default. When OFF, if
the requested standard is not supported by the compiler, CMake will decay the
request to an earlier standard rather than halting with an error. This decaying
behavior is often unexpected for new developers and in practice can be a cause
of confusion. Thus, for most projects, when specifying a <LANG>_STANDARD
property, its corresponding <LANG>_STANDARD_REQUIRED property will almost
always need to be set to true as well to ensure the particular requested
standard is treated as a firm requirement. When a target is created, the
initial value of this property is taken from the
CMAKE_<LANG>_STANDARD_REQUIRED variable.
<LANG>_EXTENSIONS
<LANG>_EXTENSIONS
<LANG>_EXTENSIONS属性控制是否为该特定目标启用这些扩展。请注意,对于许多编译器来说,相同的标志用于控制语言标准以及是否启用扩展。这样做的后果之一是,如果项目设置了该<LANG>_EXTENSIONS
属性,它也应该设置该<LANG>_STANDARD属性,否则
<LANG>_EXTENSIONS可能会被有效地忽略。创建目标时,<LANG>_EXTENSIONS将从变量中获取属性的初始值CMAKE_<LANG>_EXTENSIONS。
<LANG>_EXTENSIONS target property controls whether those
extensions are enabled for that particular target.
Be aware that for many compilers, the same flag is used to control both the
language standard and whether or not extensions are enabled.
One consequence of this is that if a project sets the <LANG>_EXTENSIONS
property, it should also set the <LANG>_STANDARD property or else
<LANG>_EXTENSIONS may effectively be ignored.
When a target is created, the initial value of the <LANG>_EXTENSIONS property
is taken from the CMAKE_<LANG>_EXTENSIONS variable.
在实践中,项目通常会设置为上述目标属性提供默认值的变量,而不是直接设置目标属性。这可确保项目中的所有目标都以一致的方式使用兼容的设置构建。还建议项目设置全部三个而不是仅其中一些。事实证明,对于许多开发人员来说,<LANG>_STANDARD_REQUIRED和的默认设置相对不直观。<LANG>_EXTENSIONS通过显式设置它们,项目可以明确其期望的标准行为。
In practice, projects would more typically set the variables that provide the
defaults for the above target properties rather than setting the target
properties directly.
This ensures that all targets in a project are built in a consistent manner
with compatible settings.
It is also recommended that projects set all three rather than just some of
them.
The defaults for <LANG>_STANDARD_REQUIRED and <LANG>_EXTENSIONS have
proven to be relatively unintuitive for many developers.
By explicitly setting them, a project makes clear the standard behavior it
expects.
# Require C++11 and disable extensions for all targets
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)# Require C++11 and disable extensions for all targets
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
当使用 GCC 或 Clang 时,上面通常会添加该
-std=c++11标志。对于 VS2015 Update 3 之前的 Visual Studio 编译器,不会添加任何标志,因为编译器要么默认支持 C++11,要么根本不支持 C++11。从 Visual Studio 15 Update 3 开始,编译器支持指定 C++14 或更高版本的 C++ 标准,如果未设置,则默认为 C++14。
When using GCC or Clang, the above would typically add the
-std=c++11 flag.
For Visual Studio compilers before VS2015 Update 3, no flags would be added
since the compiler either supports C++11 by default or it has no support for
C++11 at all.
From Visual Studio 15 Update 3, the compiler supports specifying a C++
standard of C++14 or later, with C++14 being the default if not set.
相比之下,以下示例请求更高的 C++ 版本并启用编译器扩展,从而产生类似的 GCC/Clang 编译器标志
-std=gnu++14。Visual Studio 编译器可能默认支持或不支持所请求的标准,具体取决于编译器版本。如果使用的编译器不支持请求的 C++ 标准,CMake 将配置编译器以使用它支持的最新 C++ 标准。
In comparison, the following example requests a later C++ version and enables
compiler extensions, resulting in a GCC/Clang compiler flag like
-std=gnu++14 instead. Visual Studio compilers again may support
the requested standard by default or not, depending on compiler version. If the
compiler in use does not support the requested C++ standard, CMake will
configure the compiler to use the most recent C++ standard it supports.
# Use C++14 if available and allow compiler extensions
# for all targets
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED OFF)
set(CMAKE_CXX_EXTENSIONS ON)# Use C++14 if available and allow compiler extensions
# for all targets
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED OFF)
set(CMAKE_CXX_EXTENSIONS ON)
以下示例显示如何仅为特定目标设置 C 标准详细信息:
The following example shows how to set the C standard details for a specific target only:
# Build target Foo with C99, no compiler extensions
set_target_properties(Foo PROPERTIES
C_STANDARD 99
C_STANDARD_REQUIRED ON
C_EXTENSIONS OFF
)# Build target Foo with C99, no compiler extensions
set_target_properties(Foo PROPERTIES
C_STANDARD 99
C_STANDARD_REQUIRED ON
C_EXTENSIONS OFF
)
请注意,<LANG>_STANDARD指定了最低标准,不一定是确切的要求。由于编译功能要求(接下来讨论),CMake 可能会选择更新的标准。
Note that <LANG>_STANDARD specifies a minimum standard, not necessarily an
exact requirement.
CMake may select a more recent standard due to compile feature requirements
(discussed next).
直接为目标或整个项目设置语言标准是管理标准需求的最简单方法。当项目开发人员知道哪个语言版本提供项目代码使用的功能时,这是最合适的方法。当使用大量语言特征时,它特别方便,因为不必明确指定每个特征。然而,在某些情况下,开发人员可能更愿意声明其代码使用哪种语言功能,并让 CMake 选择适当的语言标准。这样做的优点是,与直接指定标准不同,编译功能要求可以是目标接口的一部分,因此可以在链接到它的其他目标上强制执行。
Directly setting the language standard for a target or for a whole project is the simplest way to manage standard requirements. It is the most suitable approach when the project’s developers know which language version provides the features used by the project’s code. It is particularly convenient when a large number of language features are being used, since each feature does not have to be explicitly specified. In some cases, however, developers may prefer to state which language features their code uses and leave CMake to select the appropriate language standard. This has the advantage that, unlike specifying the standard directly, compile feature requirements can be part of a target’s interface and therefore can be enforced on other targets linking to it.
编译功能要求由目标属性
COMPILE_FEATURES和控制INTERFACE_COMPILE_FEATURES,但这些属性通常使用target_compile_features()命令填充,而不是直接操作。target_…()此命令遵循与CMake 提供的各种其他命令非常相似的形式:
Compile feature requirements are controlled by the target properties
COMPILE_FEATURES and INTERFACE_COMPILE_FEATURES, but these properties
are typically populated using the target_compile_features() command rather
than being manipulated directly. This command follows a very similar form to
the various other target_…() commands provided by CMake:
target_compile_features(targetName
<PRIVATE|PUBLIC|INTERFACE> feature1 [feature2 ...]
[<PRIVATE|PUBLIC|INTERFACE> feature3 [feature4 ...]]
...
)target_compile_features(targetName
<PRIVATE|PUBLIC|INTERFACE> feature1 [feature2 ...]
[<PRIVATE|PUBLIC|INTERFACE> feature3 [feature4 ...]]
...
)
、PRIVATE和关键字具有其通常的含义,控制PUBLIC如何INTERFACE应用列出的功能。PRIVATE特征填充COMPILE_FEATURES属性,该属性应用于目标本身。使用关键字指定的那些功能INTERFACE会填充该
INTERFACE_COMPILE_FEATURES属性,该属性适用于链接到 的任何目标targetName。指定为 的功能PUBLIC将添加到这两个属性中,因此将应用于目标本身以及链接到它的任何其他目标。
The PRIVATE, PUBLIC and INTERFACE keywords have their usual meanings,
controlling how the listed features should be applied. PRIVATE features
populate the COMPILE_FEATURES property, which is applied to the target
itself. Those features specified with the INTERFACE keyword populate the
INTERFACE_COMPILE_FEATURES property, which is applied to any target that
links to targetName. Features specified as PUBLIC will be added to both
properties and will therefore be applied to both the target itself and to any
other target which links to it.
每个功能必须是底层编译器支持的功能之一。CMake 提供了两个已知功能列表:CMAKE_<LANG>_KNOWN_FEATURES
其中包含该语言的所有已知功能,并且
CMAKE_<LANG>_COMPILE_FEATURES仅包含编译器支持的功能。如果编译器不支持所请求的功能,CMake 将报告错误。开发人员可能会发现变量的 CMake 文档CMAKE_<LANG>_KNOWN_FEATURES是特别有用的资源,因为它不仅列出了特定版本的 CMake 理解的功能,还包含与每个功能相关的标准文档的引用。请注意,并非特定语言版本提供的所有功能都可以使用编译功能显式指定。例如,新的 C++ STL 类型、函数等没有关联的功能。
Each feature must be one of the features supported by the underlying compiler.
CMake provides two lists of known features: CMAKE_<LANG>_KNOWN_FEATURES
which contains all known features for the language and
CMAKE_<LANG>_COMPILE_FEATURES which contains only those features
supported by the compiler. If a requested feature is not supported by the
compiler, CMake will report an error. Developers may find the CMake
documentation for the CMAKE_<LANG>_KNOWN_FEATURES variables to be a
particularly useful resource, since it not only lists the features understood
by that particular version of CMake, it also contains references to standard
documents relating to each feature. Note that not all functionality
provided by a particular language version can be explicitly specified using
compile features. For example, new C++ STL types, functions, etc. have no
associated feature.
从 CMake 3.8 开始,每个语言的元功能可用于指示特定的语言标准,而不是特定的编译功能。这些元功能采用以下形式<lang>_std_<value>,当被列为必需的编译功能时,CMake 将确保使用启用该语言标准的编译器标志。例如,要添加编译功能以确保目标和链接到它的任何内容都启用了 C++14 支持,可以使用以下内容:
From CMake 3.8, a per language meta-feature is available to indicate a
particular language standard rather than a specific compile feature. These
meta-features take the form <lang>_std_<value> and when listed as a required
compile feature, CMake will ensure compiler flags are used which enable that
language standard. For example, to add a compile feature which ensures that a
target and anything that links against it has C++14 support enabled, the
following could be used:
target_compile_features(targetName PUBLIC cxx_std_14)target_compile_features(targetName PUBLIC cxx_std_14)
如果项目需要支持 3.8 之前的 CMake 版本,则上述元功能将不可用。在这种情况下,每个编译功能都必须单独列出,这可能不切实际并且可能不完整。一般来说,这往往会限制编译功能的有用性,项目经常选择通过上一节中描述的目标属性来设置语言标准。
If a project needs to support CMake versions earlier than 3.8, then the above meta-feature will not be available. In such cases, each compile feature would have to be listed out individually, which can be impractical and would likely be incomplete. This tends to limit the usefulness of compile features in general, with projects frequently choosing to set the language standard through the target properties described in the previous section instead.
在目标同时<LANG>_STANDARD指定其属性集和编译功能(直接或间接地作为其链接到的功能的结果INTERFACE
)的情况下,CMake 将强制执行更严格的标准要求。在以下示例中,将使用 C++14、
C++17 和C++14Foo构建:BarGuff
In situations where a target has both its <LANG>_STANDARD property set and
compile features specified (directly or transitively as a result of INTERFACE
features from something it links to), CMake will enforce the stronger standard
requirement. In the following example, Foo would be built with C++14, Bar
with C++17 and Guff with C++14:
set_target_properties(Foo PROPERTIES CXX_STANDARD 11)
target_compile_features(Foo PUBLIC cxx_std_14)
set_target_properties(Bar PROPERTIES CXX_STANDARD 17)
target_compile_features(Bar PRIVATE cxx_std_11)
set_target_properties(Guff PROPERTIES CXX_STANDARD 11)
target_link_libraries(Guff PRIVATE Foo)set_target_properties(Foo PROPERTIES CXX_STANDARD 11)
target_compile_features(Foo PUBLIC cxx_std_14)
set_target_properties(Bar PROPERTIES CXX_STANDARD 17)
target_compile_features(Bar PRIVATE cxx_std_11)
set_target_properties(Guff PROPERTIES CXX_STANDARD 11)
target_link_libraries(Guff PRIVATE Foo)
请注意,这可能意味着可以使用比项目预期更新的语言标准,这在某些情况下可能会导致编译错误。例如,C++17 删除了std::auto_ptr,因此如果代码期望使用较旧的语言标准进行编译并且仍然使用std::auto_ptr,则如果工具链严格强制执行此删除,则可能无法编译。
Note that this may mean a more recent language standard could be used than what
the project expected, which in some cases can result in compilation errors. For
example, C++17 removed std::auto_ptr, so if code expects to be compiled
with an older language standard and still uses std::auto_ptr, it could fail
to compile if the toolchain strictly enforces this removal.
、CUDA和语言有点不寻常OBJC,OBJCXX因为它们基于 C 或 C++。和语言还没有自己独立的编译功能集,但可以使用相应基础语言的编译功能OBJC。OBJCXX在 CMake 3.16 或更早版本中,该CUDA语言也没有自己的单独的编译功能集,而是依赖于 C++ 编译功能。CMake 3.17 添加了对CUDA.
The CUDA, OBJC and OBJCXX languages are a little unusual in that they are
based off C or C++.
The OBJC and OBJCXX languages do not as yet have their own separate set of
compile features, but the compile features for the corresponding base language
can be used instead.
In CMake 3.16 or earlier, the CUDA language also did not have its own
separate set of compile features and relied on the C++ compile features.
CMake 3.17 added dedicated compile feature support for CUDA.
有些项目能够处理支持或不支持的特定语言功能。例如,它们可能提供后备实现,或者仅在编译器支持的情况下定义某些函数重载。项目可能支持一些可选的编译功能,例如旨在指导开发人员或为编译器提供捕获常见错误的增强能力的关键字。C++ 关键字(例如final和 )override
就是常见的示例。
Some projects have the ability to handle a particular language feature being
supported or not. They may provide a fall back implementation, for example, or
only define certain function overloads if they are supported by the compiler. A
project may support some compile features being optional, such as keywords
intended to guide the developer or provide an increased ability for the
compiler to catch common mistakes. C++ keywords such as final and override
are common examples of this.
CMake 提供了多种方法来处理上述场景。一种方法是使用生成器表达式有条件地设置编译器定义或根据特定编译功能的可用性包含目录。这些可能有点冗长,但它们提供了很大的灵活性,并支持基于特征的功能的非常精确的处理。考虑以下示例:
CMake provides a number of ways to handle the above scenarios. One approach is to use generator expressions to conditionally set compiler defines or include directories based on the availability of a particular compile feature. These can be a little verbose, but they offer a great deal of flexibility and support very precise handling of feature-based functionality. Consider the following example:
add_library(Foo ...)
# Make override a feature requirement only if available
target_compile_features(Foo PUBLIC
$<$<COMPILE_FEATURES:cxx_override>:cxx_override>
)
# Define the foo_OVERRIDE symbol so it provides the
# override keyword if available or empty otherwise
set(withKeyword -Dfoo_OVERRIDE=override)
set(noKeyword -Dfoo_OVERRIDE)
target_compile_definitions(Foo PUBLIC
$<$<COMPILE_FEATURES:cxx_override>:${withKeyword}>
$<$<NOT:$<COMPILE_FEATURES:cxx_override>>:${noKeyword}>
)add_library(Foo ...)
# Make override a feature requirement only if available
target_compile_features(Foo PUBLIC
$<$<COMPILE_FEATURES:cxx_override>:cxx_override>
)
# Define the foo_OVERRIDE symbol so it provides the
# override keyword if available or empty otherwise
set(withKeyword -Dfoo_OVERRIDE=override)
set(noKeyword -Dfoo_OVERRIDE)
target_compile_definitions(Foo PUBLIC
$<$<COMPILE_FEATURES:cxx_override>:${withKeyword}>
$<$<NOT:$<COMPILE_FEATURES:cxx_override>>:${noKeyword}>
)
上面的代码将允许任何 C++ 编译器编译如下代码,无论它是否支持关键字override:
The above would allow code such as the following to compile for any C++
compiler, regardless of whether or not it supported the override keyword:
class MyClass : public Base
{
public:
void func() foo_OVERRIDE;
...
};class MyClass : public Base
{
public:
void func() foo_OVERRIDE;
...
};
其他特征也可以具有以大致相同的方式使用的类似条件定义符号。C++ 关键字(例如final,constexpr和 )noexcept如果可用的话可以使用,如果编译器不支持则可以省略,但仍然会生成有效的代码。其他关键字(例如nullptr和 )static_assert
具有替代实现,如果不支持该关键字,可以使用这些替代实现。
Other features can also have a similar conditionally defined symbol used in
much the same way.
C++ keywords like final, constexpr and noexcept can potentially be used
if available or omitted if not supported by the compiler and still produce
valid code. Other keywords such as nullptr and static_assert
have alternative implementations which can be used if the keyword is not
supported.
为每个功能指定生成器表达式以涵盖支持和不支持的情况可能既乏味又脆弱。使用模块(从 CMake 3.20 开始已弃用)等技术WriteCompilerDetectionHeader可以帮助减少缺点,但从长远来看,在标准级别而不是单个功能上切换行为可能是更好的方法。
Specifying generator expressions for each feature to cover the supported and
unsupported cases can be both tedious and fragile.
Techniques such as using the WriteCompilerDetectionHeader module (which
is deprecated as of CMake 3.20) can help reduce the drawbacks, but in the long
run, switching behavior at the standard level rather than on individual
features is likely to be the better approach.
项目应避免直接设置编译器和链接器标志来控制所使用的语言标准。所需的标志因编译器而异,因此使用 CMake 提供的功能并允许其适当地填充标志会更健壮、更易于维护且更方便。该
CMakeLists.txt文件还将更清楚地表达意图,因为使用人类可读的变量和属性而不是通常神秘的原始编译器和链接器标志。
Projects should avoid setting compiler and linker flags directly to control the
language standard used. The required flags vary from compiler to compiler, so
it is more robust, more maintainable and more convenient to use the features
CMake provides and allow it to populate the flags appropriately. The
CMakeLists.txt file will also more clearly express the intent, since human
readable variables and properties are used instead of often cryptic raw
compiler and linker flags.
控制语言标准要求的最简单方法是使用CMAKE_<LANG>_STANDARD,CMAKE_<LANG>_STANDARD_REQUIRED和
CMAKE_<LANG>_EXTENSIONS变量。这些可用于设置整个项目的语言标准行为,确保所有目标的使用一致。project()理想情况下,这些变量应该在顶级文件中的第一个命令之后设置CMakeLists.txt。项目应始终将所有三个变量设置在一起,以明确应如何强制执行语言标准要求以及是否允许编译器扩展。省略CMAKE_<LANG>_STANDARD_REQUIRED或
CMAKE_<LANG>_EXTENSIONS可能会导致意外行为,因为默认值可能不是某些开发人员直观期望的。
The simplest method for controlling language standard requirements is to use
the CMAKE_<LANG>_STANDARD, CMAKE_<LANG>_STANDARD_REQUIRED and
CMAKE_<LANG>_EXTENSIONS variables. These can be used to set the language
standard behavior for the entire project, ensuring consistent usage across all
targets. These variables should ideally be set just
after the first project() command in the top level CMakeLists.txt file.
Projects should always set all three variables together to make clear
how the language standard requirements should be enforced and whether compiler
extensions are permitted. Omitting CMAKE_<LANG>_STANDARD_REQUIRED or
CMAKE_<LANG>_EXTENSIONS can lead to unexpected behavior, as the
defaults may not be what some developers intuitively expect.
如果仅需要对某些目标而不是其他目标强制执行语言标准,则<LANG>_STANDARD可以在单个目标上设置<LANG>_STANDARD_REQUIRED和
目标属性,而不是为整个项目设置。<LANG>_EXTENSIONS这些属性的行为就像它们一样
PRIVATE,这意味着它们仅指定对该目标的要求,而不指定链接到该目标的任何内容的要求。这给项目带来了更多的负担,以确保所有目标都正确指定了语言标准细节。在实践中,使用变量在项目范围内设置语言要求通常比使用每个目标属性更容易、更稳健。优先使用变量,除非项目需要针对不同目标的不同语言标准行为。
If the language standard only needs to be enforced for some targets and not
others, the <LANG>_STANDARD, <LANG>_STANDARD_REQUIRED and
<LANG>_EXTENSIONS target properties can be set on individual targets rather
than for the whole project. These properties behave as though they were
PRIVATE, meaning they only specify requirements on that target and not on
anything linking to it. This places more of a burden on the project
to ensure that all targets have correctly specified language standard details.
In practice, it is usually easier and more robust to use the variables to set
language requirements project-wide rather than use per target properties.
Prefer using the variables unless the project needs different language
standard behavior for different targets.
如果使用 CMake 3.8 或更高版本,则可以使用编译功能在每个目标的基础上指定所需的语言标准。该
target_compile_features()命令使这变得简单并清楚地指定此类要求是否是PRIVATE,PUBLIC或INTERFACE。PUBLIC以这种方式指定语言要求的主要优点是它可以通过和关系在其他目标上传递INTERFACE
。导出和安装目标时,这些要求也会保留(请参阅第 26 章,安装)。但请注意,仅提供了
<LANG>_STANDARD
和target 属性行为的等效项,因此仍应使用target 属性或变量来控制是否允许编译器扩展。由于编译器和链接器经常将两者组合成单个标志,这些属性/变量通常只有在也设置了相应的属性/变量时才会生效,因此即使使用编译功能,最终也很难避免必须指定。<LANG>_STANDARD_REQUIRED<LANG>_EXTENSIONSCMAKE_<LANG>_EXTENSIONS…EXTENSIONS<LANG>_STANDARD<LANG>_STANDARD
If using CMake 3.8 or later, compile features can be used to specify the
desired language standard on a per target basis. The
target_compile_features() command makes this easy and clearly specifies
whether such requirements are PRIVATE, PUBLIC or INTERFACE. The main
advantage of specifying a language requirement this way is that it can be
enforced transitively on other targets via PUBLIC and INTERFACE
relationships. These requirements are also preserved when targets are exported
and installed (see Chapter 26, Installing).
Note, however, that only the equivalent of the <LANG>_STANDARD
and <LANG>_STANDARD_REQUIRED target property behaviors are provided, so the
<LANG>_EXTENSIONS target property or CMAKE_<LANG>_EXTENSIONS variable
should still be used to control whether or not compiler extensions are allowed.
These …EXTENSIONS properties/variables often only take effect if the
corresponding <LANG>_STANDARD is also set due to how compilers and linkers
frequently combine the two into a single flag, so ultimately it is difficult to
escape having to specify <LANG>_STANDARD even when compile features are used.
指定单独的编译功能可以对每个目标级别的语言要求进行细粒度的控制。在实践中,开发人员很难确保目标使用的所有功能都被明确指定,因此总会存在语言需求是否正确定义的问题。随着代码开发的不断进行,它们也很容易过时。大多数项目可能会发现以这种方式指定语言要求既乏味又脆弱,因此只有在情况明确需要时才应使用它们。
Specifying individual compile features provides fine grained control over the language requirements at a per target level. In practice, it is difficult for developers to ensure that all features used by a target are explicitly specified, so there will always be the question of whether the language requirements are properly defined. They can also easily become out of date as code development continues over time. Most projects will probably find specifying language requirements this way to be tedious and fragile, so they should only be used if the situation clearly warrants it.
在 C++11 和 C++14 的早期,使用编译功能可能很有用,因为编译器支持通常滞后。对于后续的语言版本,主流编译器的支持速度要快得多。随后,CMake 停止为 C++14 以外的语言标准提供细粒度功能。cxx_std_17对于 C++17 及更高版本,仅提供诸如此类的高级元功能。
In the early days of C++11 and C++14, working with compile features was
potentially useful because compiler support was often lagging behind.
For later language releases, support by the mainstream compilers has arrived
much faster.
Subsequently, CMake stopped providing fine-grained features for language
standards beyond C++14.
For C++17 and later, only the high level meta features like cxx_std_17 are
provided.
对于仍需要支持较旧编译器的项目,它们可以检测可用的编译功能并提供功能是否可用的实现。CMake 提供的模块WriteCompilerDetectionHeader有时有助于帮助项目过渡到更现代的编译器,但该模块在 CMake 3.20 中已被弃用。因此,不应再使用它。
For projects still needing to support older compilers, they can detect
available compile features and provide implementations for whether a feature is
available or not.
The WriteCompilerDetectionHeader module provided by CMake was sometimes
useful in helping projects transition to more modern compilers, but that
module was deprecated in CMake 3.20.
Therefore, it should no longer be used.
CMake 支持多种目标类型,而不仅仅是第 4 章“构建简单目标”中介绍的简单可执行文件和库。可以定义不同的目标类型作为对其他实体的引用,而不是自行构建。它们可用于将传递属性和依赖项收集在一起,而无需实际生成自己的二进制文件,或者它们甚至可以是一种库,只是对象文件的集合,而不是传统的静态或共享库。许多事物可以被抽象为目标,以隐藏平台差异、文件系统中的位置、文件名等的复杂性。本章涵盖所有这些不同的目标类型并讨论它们的用途。
CMake supports a wide variety of target types, not just the simple executables and libraries introduced back in Chapter 4, Building Simple Targets. Different target types can be defined that act as a reference to other entities rather than being built themselves. They can be used to collect together transitive properties and dependencies without actually producing their own binaries, or they can even be a kind of library that is simply a collection of object files rather than a traditional static or shared library. Many things can be abstracted away as a target to hide the complexities of platform differences, locations in the filesystem, file names and so on. This chapter covers all of these various target types and discusses their uses.
另一类目标是实用程序或自定义目标。它们可用于执行任意命令并定义自定义构建规则,从而允许项目实现所需的任何类型的行为。它们有自己的专用命令和独特的行为,并将在下一章中深入介绍。
Another category of target is the utility or custom target. These can be used to execute arbitrary commands and define custom build rules, allowing projects to implement just about any sort of behavior needed. They have their own dedicated commands and unique behaviors and are covered in depth in the next chapter.
该add_executable()命令不仅仅具有第 4 章“构建简单目标”中介绍的形式。还存在另外两种形式,可用于定义引用其他事物的可执行目标。全套支持的表格是:
The add_executable() command has more than just the form introduced back
in Chapter 4, Building Simple Targets. Two other forms also exist which can be used to
define executable targets that reference other things. The full set of
supported forms are:
add_executable(targetName
[WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)
add_executable(targetName IMPORTED [GLOBAL])
add_executable(aliasName ALIAS targetName)add_executable(targetName
[WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)
add_executable(targetName IMPORTED [GLOBAL])
add_executable(aliasName ALIAS targetName)
该IMPORTED表单可用于为现有可执行文件(而不是项目构建的可执行文件)创建 CMake 目标。通过创建一个代表可执行文件的目标,项目的其他部分可以像对待项目本身构建的任何其他可执行目标一样对待它(有一些限制)。最显着的好处是,它可以在 CMake 自动将目标名称替换为其在磁盘上的位置的上下文中使用,例如在执行测试或自定义任务命令时(均在后面的章节中介绍)。与常规目标相比的少数差异之一是无法安装导入的目标,
第 26 章“安装”中介绍了该主题。
The IMPORTED form can be used to create a CMake target for an existing
executable rather than one built by the project. By creating a target to
represent the executable, other parts of the project can treat it just like it
would any other executable target that the project built itself (with some
restrictions). The most significant benefit is that it can be used in contexts
where CMake automatically replaces a target name with its location on disk,
such as when executing commands for tests or custom tasks (both covered in
later chapters). One of the few differences compared to a regular target is
that imported targets cannot be installed, a topic covered in
Chapter 26, Installing.
定义导入的可执行目标时,需要设置某些目标属性才能使用。任何导入目标的大多数相关属性的名称都以 开头IMPORTED,但对于可执行文件来说,
IMPORTED_LOCATION和IMPORTED_LOCATION_<CONFIG>是最重要的。当需要导入的可执行文件的位置时,CMake 将首先查看特定于配置的属性,只有在未设置该属性的情况下,它才会查看更通用的IMPORTED_LOCATION属性。通常,该位置不需要特定于配置,因此通常只需IMPORTED_LOCATION设置即可。
When defining an imported executable target, certain target properties need to
be set before it can be useful. Most of the relevant properties for any
imported target have names beginning with IMPORTED, but for executables,
IMPORTED_LOCATION and IMPORTED_LOCATION_<CONFIG> are the most
important. When the location of the imported executable is needed, CMake will
first look at the configuration-specific property and only if that is not set
will it look at the more generic IMPORTED_LOCATION property. Typically, the
location doesn’t need to be configuration-specific, so it is very common for
only IMPORTED_LOCATION to be set.
当不使用GLOBAL关键字定义时,导入的目标仅在当前目录范围及以下目录范围内可见,但添加后GLOBAL会使目标在任何地方都可见。相比之下,项目构建的常规可执行目标始终是全局的。其原因以及目标可见性降低的一些相关影响将在
下面第 17.3 节“促进导入目标”中进一步介绍。
When defined without the GLOBAL keyword, an imported target will only be
visible in the current directory scope and below, but adding GLOBAL makes the
target visible everywhere. In contrast, regular executable targets built by the
project are always global. The reasons for this and some of the associated
implications of reduced target visibility are covered in
Section 17.3, “Promoting Imported Targets” further below.
目标ALIAS是在 CMake 中引用另一个目标的只读方式。它可用于读取其别名的目标的属性,并且可用于自定义命令和测试命令,就像别名目标一样(分别参见第
18.1节“自定义目标”和第 25.1 节“定义和执行简单测试”)。别名不会使用别名创建新的构建目标。定义和使用别名有一些限制:
An ALIAS target is a read-only way to refer to another target within CMake.
It can be used to read properties of the target it aliases and it may be used
in custom commands and test commands just like the aliased target (see
Section 18.1, “Custom Targets” and Section 25.1, “Defining And Executing A Simple Test” respectively).
An alias does not create a new build target with the alias name.
There are limitations to defining and using aliases:
该add_library()命令也有多种不同的形式。库的详细信息比可执行文件更复杂,这是库在项目中扮演的各种角色的结果。
The add_library() command also has a number of different forms.
The details for libraries are more involved than for executables, which is a
consequence of the variety of roles that libraries can take in a project.
第 4 章“构建简单目标”中介绍的基本形式可用于定义大多数开发人员熟悉的库的常见类型:
The basic form introduced back in Chapter 4, Building Simple Targets can be used to define the common types of libraries most developers are familiar with:
add_library(targetName
[STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)add_library(targetName
[STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)
如果没有给出STATIC,SHARED或MODULE关键字,则库将为STATIC或SHARED。该选择由变量的值决定BUILD_SHARED_LIBS(请参见第 21.1 节“构建基础知识”)。
If no STATIC, SHARED or MODULE keyword is given, the library will be
either STATIC or SHARED.
The choice is determined by the value of the BUILD_SHARED_LIBS variable
(see Section 21.1, “Build Basics”).
该add_library()命令还可用于定义对象库。这些是未合并到单个存档或共享库中的目标文件的集合:
The add_library() command can also be used to define object libraries.
These are a collection of object files that are not combined into a single
archive or shared library:
add_library(targetName OBJECT
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)add_library(targetName OBJECT
[EXCLUDE_FROM_ALL]
source1 [source2 ...]
)
对于 CMake 3.11 或更早版本,对象库无法像其他库类型一样链接(即它们不能与 一起使用target_link_libraries())。它们需要使用以下形式的生成器表达式
$<TARGET_OBJECTS:objLib>作为另一个可执行文件或库目标的源列表的一部分。由于它们无法链接,因此它们不向作为对象/源添加到的目标提供传递依赖项。这可能使它们不如其他库类型方便,因为头搜索路径、编译器定义等必须手动传递到它们添加到的目标。
With CMake 3.11 or earlier, object libraries cannot be linked like other
library types (i.e. they cannot be used with target_link_libraries()).
They require using a generator expression of the form
$<TARGET_OBJECTS:objLib> as part of the list of sources of another
executable or library target.
Because they cannot be linked, they therefore do not provide transitive
dependencies to the targets they are added to as objects/sources.
This can make them less convenient than the other library types, since header
search paths, compiler defines, etc. have to be manually carried across to the
targets they are added to.
CMake 3.12 引入的功能使对象库的行为更像其他类型的库,但有一些注意事项。从 CMake 3.12 开始,对象库可以与 一起使用target_link_libraries(),既可以作为要添加的目标(即命令的第一个参数),也可以作为要添加的库之一。但由于它们添加对象文件而不是实际的库,因此它们的传递性质受到更多限制,以防止对象文件被多次添加到消费目标。一个简单的解释是,对象文件仅添加到直接链接到对象库的目标,而不是传递到该目标库之外。然而,对象库的使用要求确实像普通库一样进行传递传播。
CMake 3.12 introduced features that make object libraries behave more like
other types of libraries, but with some caveats. From CMake 3.12, object
libraries can be used with target_link_libraries(), either as the target
being added to (i.e. the first argument to the command) or as one of the
libraries being added. But because they add object files rather than actual
libraries, their transitive nature is more restricted to prevent object files
from being added multiple times to consuming targets. A simplistic explanation
is that object files are only added to a target that links directly to the
object library, not transitively beyond that. The object library’s usage
requirements do, however, propagate transitively exactly like an ordinary
library would.
如果来自非 CMake 项目基于源或对象文件而不是一组相关的静态库定义其目标的背景,一些开发人员可能会发现对象库更自然。然而,一般来说,如果有选择,静态库通常是 CMake 项目中更方便的选择。在依赖 CMake 3.12 及更高版本中对象库可用的扩展功能之前,请考虑普通静态库是否更合适并且最终更易于使用。
Some developers may find object libraries more natural if coming from a background where non-CMake projects defined their targets based on sources or object files rather than a related set of static libraries. In general, however, where there is a choice, static libraries will typically be the more convenient choice in CMake projects. Before relying on the expanded features available for object libraries in CMake 3.12 and later, consider whether an ordinary static library is more appropriate and ultimately easier to use.
就像可执行文件一样,库也可以定义为导入的目标。这些被打包期间创建的配置文件或 Find 模块实现(在第 24 章“查找事物”和第 26 章“安装”中介绍)大量使用,但在这些上下文之外的使用有限。它们不定义由项目构建的库,而是充当对外部提供的库的引用(例如,它已经存在于系统上,由当前 CMake 项目外部的某个进程构建或提供)通过配置文件所属的包)。
Just like executables, libraries may also be defined as imported targets. These are heavily used by config files created during packaging or by Find module implementations (covered in Chapter 24, Finding Things and Chapter 26, Installing), but have limited use outside of those contexts. They don’t define a library to be built by the project, rather they act as a reference to a library that is provided externally (e.g. it already exists on the system, is built by some process outside of the current CMake project or is provided by the package that a config file is part of).
add_library(targetName
(STATIC | SHARED | MODULE | OBJECT | UNKNOWN)
IMPORTED [GLOBAL]
)add_library(targetName
(STATIC | SHARED | MODULE | OBJECT | UNKNOWN)
IMPORTED [GLOBAL]
)
库类型必须紧接在targetName. 如果新目标将引用的库类型已知,则应如此指定。这将允许 CMake 在各种情况下将导入的目标视为指定类型的常规库目标。该类型只能使用 CMake 3.9 或更高版本设置OBJECT(该版本之前不支持导入的对象库)。如果库类型未知,UNKNOWN
则应给出类型,在这种情况下,CMake 将仅使用库的完整路径,而无需在链接器命令行等位置进行进一步解释。这意味着更少的检查,并且在 Windows 构建的情况下,无需处理 DLL 导入库。
The library type must be given immediately after the targetName. If the type
of library that the new target will refer to is known, it should be specified
as such. This will allow CMake to treat the imported target just like a regular
library target of the named type in various situations. The type can only be
set to OBJECT with CMake 3.9 or later (imported object libraries were not
supported before that version). If the library type is not known, the UNKNOWN
type should be given, in which case CMake will simply use the full path to the
library without further interpretation in places like linker command lines.
This will mean fewer checks and in the case of Windows builds, no handling of
DLL import libraries.
除了库之外,导入的目标所代表的文件系统上的位置需要由和/或
属性OBJECT指定(即与导入的可执行文件相同)。对于 Windows 平台,应设置两个属性:
应保存 DLL 的位置,并
应保存关联的导入库的位置,该库通常具有
文件扩展名(也可以设置这些属性的变体,并且将采用优先)。对于对象库,必须将该属性设置为导入目标表示的对象文件列表,而不是上述位置属性。IMPORTED_LOCATIONIMPORTED_LOCATION_<CONFIG>IMPORTED_LOCATIONIMPORTED_IMPLIB.lib…_<CONFIG>IMPORTED_OBJECTS
Except for OBJECT libraries, the location on the filesystem that the imported
target represents needs to be specified by the IMPORTED_LOCATION and/or
IMPORTED_LOCATION_<CONFIG> properties (i.e. the same as for imported
executables). In the case of Windows platforms, two properties should be set:
IMPORTED_LOCATION should hold the location of the DLL and IMPORTED_IMPLIB
should hold the location of the associated import library, which usually has a
.lib file extension (the …_<CONFIG> variants of these properties can also
be set and will take precedence). For object libraries, instead of the above
location properties, the IMPORTED_OBJECTS property must be set to a list
of object files that the imported target represents.
导入的库还支持许多其他目标属性,其中大多数通常可以单独保留或由 CMake 自动设置。需要手动编写配置包的开发人员应参考 CMake 参考文档来了解IMPORTED_…可能与其情况相关的其他目标属性。大多数项目将依赖 CMake 为它们生成此类文件,因此这样做的需要应该相当罕见。
Imported libraries also support a number of other target properties, most of
which can typically be left alone or are automatically set by CMake. Developers
who need to manually write config packages should refer to the CMake reference
documentation to understand the other IMPORTED_… target properties which
may be relevant to their situation. Most projects will rely on CMake generating
such files for them though, so the need to do this should be fairly uncommon.
默认情况下,导入的库被定义为本地目标,这意味着它们仅在当前目录范围及以下目录中可见。可以给出关键字GLOBAL以使它们具有全局可见性,就像其他常规目标一样。最初创建的库可能没有关键字,但后来提升为全局可见性,该主题将在下面的第 17.3 节“提升导入目标”GLOBAL中详细介绍
。
By default, imported libraries are defined as local targets, meaning they are
only visible in the current directory scope and below. The GLOBAL keyword can
be given to make them have global visibility instead, just like other regular
targets. A library may initially be created without the GLOBAL keyword but
later promoted to global visibility, a topic covered in detail in
Section 17.3, “Promoting Imported Targets” further below.
# Windows-specific example of imported library
add_library(MyWindowsLib SHARED IMPORTED)
set_target_properties(MyWindowsLib PROPERTIES
IMPORTED_LOCATION /some/path/bin/foo.dll
IMPORTED_IMPLIB /some/path/lib/foo.lib
)# Windows-specific example of imported library
add_library(MyWindowsLib SHARED IMPORTED)
set_target_properties(MyWindowsLib PROPERTIES
IMPORTED_LOCATION /some/path/bin/foo.dll
IMPORTED_IMPLIB /some/path/lib/foo.lib
)
# Assume FOO_LIB holds the location of the library but
# its type is unknown
add_library(MysteryLib UNKNOWN IMPORTED)
set_target_properties(MysteryLib PROPERTIES
IMPORTED_LOCATION ${FOO_LIB}
)# Assume FOO_LIB holds the location of the library but
# its type is unknown
add_library(MysteryLib UNKNOWN IMPORTED)
set_target_properties(MysteryLib PROPERTIES
IMPORTED_LOCATION ${FOO_LIB}
)
# Imported object library, Windows example shown
add_library(MyObjLib OBJECT IMPORTED)
set_property(TARGET MyObjLib PROPERTY
# These .obj files would be .o on most other platforms
IMPORTED_OBJECTS /some/path/obj1.obj
/some/path/obj2.obj
)
# Regular executable target using imported object library.
# Platform differences assumed to be handled by MyObjLib.
add_executable(MyExe $<TARGET_OBJECTS:MyObjLib>)# Imported object library, Windows example shown
add_library(MyObjLib OBJECT IMPORTED)
set_property(TARGET MyObjLib PROPERTY
# These .obj files would be .o on most other platforms
IMPORTED_OBJECTS /some/path/obj1.obj
/some/path/obj2.obj
)
# Regular executable target using imported object library.
# Platform differences assumed to be handled by MyObjLib.
add_executable(MyExe $<TARGET_OBJECTS:MyObjLib>)
该命令的另一种形式add_library()允许定义接口库。这些通常并不代表物理库,而是主要用于收集使用要求和依赖关系,以应用于链接到它们的任何内容。它们的使用的一个流行示例是仅头文件库,其中没有需要链接的物理库,但头文件搜索路径、编译器定义等需要转移到使用头文件的任何内容。
Another form of the add_library() command allows interface libraries to be
defined. These do not usually represent a physical library, instead they
primarily serve to collect usage requirements and dependencies to be applied to
anything that links to them. A popular example of their use is for header-only
libraries where there is no physical library that needs to be linked, but
header search paths, compiler definitions, etc. need to be carried forward to
anything using the headers.
add_library(targetName INTERFACE)add_library(targetName INTERFACE)
所有各种target_…()命令都可以与其INTERFACE
关键字一起使用来定义接口库将承载的使用要求。也可以INTERFACE_…直接使用
set_property()或来设置相关属性set_target_properties(),但target_…()命令更安全、更易用。
All the various target_…() commands can be used with their INTERFACE
keywords to define the usage requirements the interface library will
carry. One can also set the relevant INTERFACE_… properties directly with
set_property() or set_target_properties(), but the target_…() commands
are safer and easier to use.
add_library(MyHeaderOnlyToolkit INTERFACE)
target_include_directories(MyHeaderOnlyToolkit
INTERFACE /some/path/include
)
target_compile_definitions(MyHeaderOnlyToolkit
INTERFACE COOL_FEATURE=1
$<$<COMPILE_FEATURES:cxx_std_11>:HAVE_CXX11>
)
add_executable(MyApp ...)
target_link_libraries(MyApp PRIVATE MyHeaderOnlyToolkit)add_library(MyHeaderOnlyToolkit INTERFACE)
target_include_directories(MyHeaderOnlyToolkit
INTERFACE /some/path/include
)
target_compile_definitions(MyHeaderOnlyToolkit
INTERFACE COOL_FEATURE=1
$<$<COMPILE_FEATURES:cxx_std_11>:HAVE_CXX11>
)
add_executable(MyApp ...)
target_link_libraries(MyApp PRIVATE MyHeaderOnlyToolkit)
在上面的示例中,MyApp目标链接到
MyHeaderOnlyToolkit接口库。MyApp编译源代码时,它们将/some/path/include具有标头搜索路径,并且还将COOL_FEATURE=1在编译器命令行上提供编译器定义。如果MyApp目标是在启用 C++11 支持的情况下构建的,则它还将定义符号HAVE_CXX11。然后,头文件MyHeaderOnlyToolkit可以使用此符号来确定它们声明和定义的内容,而不是依赖于__cplusplusC++ 标准提供的符号,该符号的值对于一系列编译器来说通常不可靠。
In the above example, the MyApp target links against the
MyHeaderOnlyToolkit interface library. When the MyApp sources are compiled,
they will have /some/path/include as a header search path and will also have
a compiler definition COOL_FEATURE=1 provided on the compiler command line.
If the MyApp target is being built with C++11 support enabled, it will also
have the symbol HAVE_CXX11 defined. The headers in MyHeaderOnlyToolkit can
then use this symbol to determine what things they declare and define rather
than relying on the __cplusplus symbol provided by the C++ standard, the
value of which is often unreliable for a range of compilers.
通常,界面库不会有任何来源,但在某些情况下它是有意义的。仅标头库就是这样的例子之一。开发人员可能会对标头感兴趣,并且他们可能希望标头显示在他们的 IDE 中。由于标头不属于任何其他目标,因此它们通常不会出现。通过将它们作为源添加到界面库中,IDE 通常具有足够的信息来能够显示一个或多个目标下的标头。
Ordinarily, an interface library would not have any sources, but in some cases it can make sense. Header-only libraries are one such example. The headers are likely to be of interest to developers and they may want the headers to show up in their IDE. Since the headers are not part of any other target, they normally wouldn’t appear. By adding them to the interface library as sources, IDEs usually have enough information to be able to show the headers under one or more targets.
上例的一个特殊子情况是,在仅头文件库中生成一个或多个头文件作为构建的一部分(第
18.3 节“生成文件的命令”中介绍的主题)。使用 CMake 3.18 或更早版本,如果项目中没有任何内容实际使用接口库,则项目需要创建一个单独的自定义目标以确保生成标头。另一个限制是源不能在调用中添加到接口库,必须使用add_library()单独的调用来代替(请参见第 29.5.1 节“目标源”)。生成的代码将采用以下形式:target_sources()
A special sub-case of the above example is when one or more headers in a
header-only library are generated as part of the build (a topic covered in
Section 18.3, “Commands That Generate Files”).
With CMake 3.18 or earlier, if nothing in the project actually uses the
interface library, the project needs to create a separate custom target to
ensure that the headers will be generated.
Another limitation is that sources cannot be added to the interface library in
the add_library() call, a separate call to target_sources() has to be
used instead (see Section 29.5.1, “Target Sources”).
The resultant code would take the following form:
# Defines how to generate the header
add_custom_command(OUTPUT someHeader.h COMMAND ...)
# Required for CMake <= 3.18 to ensure header is generated
add_custom_target(GenerateSomeHeader ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/someHeader.h
)
# CMake <= 3.18 doesn't allow sources to be added to an
# INTERFACE target directly in the add_library() call
add_library(MyHeaderOnly INTERFACE)
target_sources(MyHeaderOnly
INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/someHeader.h
)# Defines how to generate the header
add_custom_command(OUTPUT someHeader.h COMMAND ...)
# Required for CMake <= 3.18 to ensure header is generated
add_custom_target(GenerateSomeHeader ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/someHeader.h
)
# CMake <= 3.18 doesn't allow sources to be added to an
# INTERFACE target directly in the add_library() call
add_library(MyHeaderOnly INTERFACE)
target_sources(MyHeaderOnly
INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/someHeader.h
)
从 CMake 3.19 开始,上述两项限制均已被删除。源可以直接在接口库的调用中列出add_library(),它们将被视为私有源。这意味着与上面的示例不同,标头不会添加到链接到接口库的目标中。相反,它们将仅与接口库本身相关联,因此只会显示在该库下的 IDE 中,而不是显示在其所有使用者中。如果添加了任何源,CMake 还将为接口库创建一个构建系统目标。像任何其他目标一样使该构建系统目标保持最新状态将确保创建其生成的源。最终结果更加简洁,并且在 IDE 中产生更好的结果:
From CMake 3.19, both of the above-mentioned restrictions have been removed.
Sources can be listed directly in the add_library() call for interface
libraries and they will be treated as private sources.
This means that unlike the example above, the headers will not be added to
targets that link to the interface library.
Instead, they will remain associated only with the interface library itself
and therefore will only show up in IDEs under that library instead of in all
of its consumers.
CMake will also create a build system target for the interface library if any
sources are added to it.
Bringing that build system target up to date like any other target will then
ensure that its generated sources are created.
The end result is both more concise and produces a better result in IDEs:
add_custom_command(OUTPUT someHeader.h COMMAND ...)
# Requires CMake 3.19 or later
add_library(MyHeaderOnly INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/someHeader.h
)add_custom_command(OUTPUT someHeader.h COMMAND ...)
# Requires CMake 3.19 or later
add_library(MyHeaderOnly INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/someHeader.h
)
接口库的另一个用途是为链接更大的库集提供便利,可能封装选择哪些库应包含在该集中的逻辑。例如:
Another use of interface libraries is to provide a convenience for linking in a larger set of libraries, possibly encapsulating logic that selects which libraries should be in the set. For example:
# Regular library targets
add_library(Algo_Fast ...)
add_library(Algo_Accurate ...)
add_library(Algo_Beta ...)
# Convenience interface library
add_library(Algo_All INTERFACE)
target_link_libraries(Algo_All INTERFACE
Algo_Fast
Algo_Accurate
$<$<BOOL:${ENABLE_ALGO_BETA}>:Algo_Beta>
)
# Other targets link to the interface library
# instead of each of the real libraries
add_executable(MyApp ...)
target_link_libraries(MyApp PRIVATE Algo_All)# Regular library targets
add_library(Algo_Fast ...)
add_library(Algo_Accurate ...)
add_library(Algo_Beta ...)
# Convenience interface library
add_library(Algo_All INTERFACE)
target_link_libraries(Algo_All INTERFACE
Algo_Fast
Algo_Accurate
$<$<BOOL:${ENABLE_ALGO_BETA}>:Algo_Beta>
)
# Other targets link to the interface library
# instead of each of the real libraries
add_executable(MyApp ...)
target_link_libraries(MyApp PRIVATE Algo_All)
仅当 CMake 选项变量为 true 时,以上内容才会包含Algo_Beta在要链接的库列表中ENABLE_ALGO_BETA。然后其他目标简单地链接到接口库Algo_All,并且条件链接Algo_Beta由接口库处理。这是一个使用接口库抽象出实际要链接、定义等内容的详细信息的示例,以便链接它们的目标不必自己实现这些详细信息。这可以用来做一些事情,比如在不同平台上抽象出完全不同的库结构,根据某些条件(变量、生成器表达式等)切换库实现,提供库结构已重构的旧库目标名称(例如分成单独的库)等等。
The above will only include Algo_Beta in the list of libraries to link if the
CMake option variable ENABLE_ALGO_BETA is true. Other targets then simply
link to Algo_All and the conditional linking of Algo_Beta is handled by the
interface library. This is an example of using an interface library to abstract
away details of what is actually going to be linked, defined, etc. so that the
targets linking against them don’t have to implement those details for
themselves. This can be exploited to do things like abstract away completely
different library structures on different platforms, switch library
implementations based on some condition (variables, generator expressions,
etc.), provide an old library target name where the library structure has been
refactored (e.g. split up into separate libraries) and so on.
add_library(targetName INTERFACE IMPORTED [GLOBAL])add_library(targetName INTERFACE IMPORTED [GLOBAL])
虽然库的用例INTERFACE通常很好理解,但添加关键字IMPORTED来生成INTERFACE IMPORTED库有时可能会引起混乱。INTERFACE当导出或安装库以供项目外部使用时,通常会出现这种组合
。INTERFACE当被另一个项目使用时,它仍然具有库的用途,但IMPORTED添加该部分以表明该库来自其他地方。这样做的效果是将库的默认可见性限制为当前目录范围而不是全局。除了下面讨论的一个例外之外,添加GLOBAL关键字以生成关键字组合会产生
INTERFACE IMPORTED GLOBAL一个库,与INTERFACE单独使用相比,实际差异很小。库INTERFACE IMPORTED不需要(实际上是禁止)设置IMPORTED_LOCATION.
While the use cases for INTERFACE libraries are generally well understood,
the addition of the IMPORTED keyword to yield an INTERFACE IMPORTED library
can sometimes be a cause of confusion. This combination usually arises when an
INTERFACE library is exported or installed for use outside of the project. It
still serves the purpose of an INTERFACE library when consumed by another
project, but the IMPORTED part is added to indicate the library came from
somewhere else. The effect of this is to restrict the default visibility of the
library to the current directory scope instead of global. With one exception
discussed below, adding the GLOBAL keyword to yield the keyword combination
INTERFACE IMPORTED GLOBAL results in a library with little practical
difference compared to INTERFACE alone. An INTERFACE IMPORTED library is
not required to (and indeed is prohibited from) setting an IMPORTED_LOCATION.
在 CMake 3.11 之前,没有任何target_…()命令可用于设置
INTERFACE_…任何类型的IMPORTED库的属性。但是,可以set_property()使用或设置这些属性
set_target_properties()。target_…()CMake 3.11 取消了使用命令设置这些属性的限制,因此虽然INTERFACE IMPORTED过去与普通IMPORTED库非常相似,但在 CMake 3.11 中,它们现在
INTERFACE在限制集方面更接近普通库。
Before CMake 3.11, none of the target_…() commands could be used to set
INTERFACE_… properties on any kind of IMPORTED library. These properties
could, however, be set using set_property() or set_target_properties().
CMake 3.11 removed the restriction on using target_…() commands to set
these properties, so whereas INTERFACE IMPORTED used to be very similar to
plain IMPORTED libraries, with CMake 3.11 they are now much closer to plain
INTERFACE libraries in terms of their set of restrictions.
下表总结了各种关键字组合支持的内容:
The following table summarizes what the various keyword combinations support:
| 关键词 | 能见度 | 导入位置 | 设置接口属性 | 可安装 |
|---|---|---|---|---|
|
全球的 Global |
禁止 Prohibited |
任何方法 Any method |
是的 Yes |
|
当地的 Local |
必需的 Required |
受限制的* Restricted* |
不 No |
|
全球的 Global |
必需的 Required |
受限制的* Restricted* |
不 No |
|
当地的 Local |
禁止 Prohibited |
受限制的* Restricted* |
不 No |
|
全球的 Global |
禁止 Prohibited |
受限制的* Restricted* |
不 No |
*各种target_…()命令可用于INTERFACE_…
通过 CMake 3.11 或更高版本设置属性。或命令可与任何 CMake 版本set_property()一起set_target_properties()使用。
* The various target_…() commands can be used to set INTERFACE_…
properties with CMake 3.11 or later.
The set_property() or set_target_properties() commands can be used with any
CMake version.
人们可能会认为不同接口和导入库组合的数量过于复杂和混乱,这是情有可原的。然而,对于大多数开发人员来说,导入的目标通常是在幕后为他们创建的,并且它们的行为或多或少与常规目标相似。在上表中的所有组合中,只有普通INTERFACE目标通常由项目直接定义。第 26 章“安装”涵盖了其他组合的大部分动机和机制。
One could be forgiven for thinking that the number of different interface and
imported library combinations is overly complicated and confusing. For most
developers, however, imported targets are generally created for them behind the
scenes and they appear to act more or less like regular targets. Of all the
combinations in the above table, only plain INTERFACE targets would typically
be defined by a project directly. Chapter 26, Installing covers much of the motivation
and mechanics of the other combinations.
该命令的最后一种形式add_library()用于定义库别名:
The last form of the add_library() command is for defining a library alias:
add_library(aliasName ALIAS otherTarget)add_library(aliasName ALIAS otherTarget)
库别名主要类似于可执行别名。它充当引用另一个库的只读方式,但不会创建新的构建目标。无法安装库别名,也无法将它们定义为另一个别名的别名。在 CMake 3.11 之前,无法为导入的目标创建库别名,但与 CMake 3.11 中对导入目标所做的其他更改一样,此限制已放宽,并且可以为全局可见的导入目标创建别名。CMake 3.18 进一步放宽了该限制,允许为非全局导入目标创建非全局别名。
A library alias is mostly analogous to an executable alias. It acts as a read-only way to refer to another library, but does not create a new build target. Library aliases cannot be installed and they cannot be defined as an alias of another alias. Before CMake 3.11, a library alias could not be created for imported targets, but as with other changes made for imported targets in CMake 3.11, this restriction was relaxed and it has become possible to create aliases for globally visible imported targets. CMake 3.18 relaxed that restriction further to allow non-global aliases to be created for non-global imported targets.
库别名有一个特别常见的用法,它与 CMake 3.0 中引入的一项重要功能相关。对于要安装或打包的每个库,常见的模式是还创建一个名称格式为 的匹配库别名projNamespace::targetName。项目中的所有此类别名通常共享相同的projNamespace. 例如:
There is a particularly common use of library aliases that relates to an
important feature introduced in CMake 3.0. For each library that will be
installed or packaged, a common pattern is to also create a matching library
alias with a name of the form projNamespace::targetName. All such
aliases within a project would typically share the same projNamespace. For
example:
# Any sort of real library (SHARED, STATIC, MODULE
# or possibly OBJECT)
add_library(MyRealThings SHARED src1.cpp ...)
add_library(OtherThings STATIC srcA.cpp ...)
# Aliases to the above with special names
add_library(BagOfBeans::MyRealThings ALIAS MyRealThings)
add_library(BagOfBeans::OtherThings ALIAS OtherThings)# Any sort of real library (SHARED, STATIC, MODULE
# or possibly OBJECT)
add_library(MyRealThings SHARED src1.cpp ...)
add_library(OtherThings STATIC srcA.cpp ...)
# Aliases to the above with special names
add_library(BagOfBeans::MyRealThings ALIAS MyRealThings)
add_library(BagOfBeans::OtherThings ALIAS OtherThings)
在项目本身内,其他目标将链接到真实目标或命名空间目标(两者具有相同的效果)。别名的动机来自于安装项目时以及其他链接到由安装/打包的配置文件创建的导入目标的链接。这些配置文件将使用命名空间名称而不是裸露的原始名称来定义导入的库(请参阅第 26.3 节“安装导出”)。然后,使用的项目将链接到命名空间名称。例如:
Within the project itself, other targets would link to either the real targets or the namespaced targets (both have the same effect). The motivation for the aliases comes from when the project is installed and something else links to the imported targets created by the installed/packaged config files. Those config files would define imported libraries with the namespaced names rather than the bare original names (see Section 26.3, “Installing Exports”). The consuming project would then link against the namespaced names. For example:
# Pull in imported targets from an installed package
find_package(BagOfBeans REQUIRED) ①
# Define an executable that links to the imported
# library from the installed package
add_executable(EatLunch main.cpp ...)
target_link_libraries(EatLunch PRIVATE
BagOfBeans::MyRealThings
)# Pull in imported targets from an installed package
find_package(BagOfBeans REQUIRED) ①
# Define an executable that links to the imported
# library from the installed package
add_executable(EatLunch main.cpp ...)
target_link_libraries(EatLunch PRIVATE
BagOfBeans::MyRealThings
)
find_package()中讨论。find_package() command is discussed in Chapter 24, Finding Things.如果在某个时候,上述项目想要将项目BagOfBeans
直接合并到自己的构建中,而不是查找已安装的包,那么它可以在不更改其链接关系的情况下这样做,因为该BagOfBeans
项目为命名空间名称提供了别名:
If at some point the above project wanted to incorporate the BagOfBeans
project directly into its own build instead of finding an installed package, it
could do so without changing its linking relationship because the BagOfBeans
project provided an alias for the namespaced name:
# Add BagOfBeans directly to this project, making
# all of its targets directly available
add_subdirectory(BagOfBeans)
# Same definition of linking relationship still works
add_executable(EatLunch main.cpp ...)
target_link_libraries(EatLunch PRIVATE
BagOfBeans::MyRealThings
)# Add BagOfBeans directly to this project, making
# all of its targets directly available
add_subdirectory(BagOfBeans)
# Same definition of linking relationship still works
add_executable(EatLunch main.cpp ...)
target_link_libraries(EatLunch PRIVATE
BagOfBeans::MyRealThings
)
具有双冒号 ( ::) 的名称的另一个重要方面是 CMake 始终将它们视为别名或导入目标的名称。任何将此类名称用于不同目标类型的尝试都会导致错误。也许更有用的是,当目标名称用作调用的一部分时
target_link_library(),如果 CMake 不知道该名称的目标,它将在生成时发出错误。将此与普通名称进行比较,如果 CMake 不知道该名称的目标,则 CMake 会将其视为假定由系统提供的库。这可能会导致错误在构建时很久之后才变得明显。
Another important aspect of names having a double-colon (::) is that CMake
will always treat them as the name of an alias or imported target. Any attempt
to use such a name for a different target type will result in an error. Perhaps
more usefully though, when the target name is used as part of a
target_link_library() call, if CMake doesn’t know of a target by that name,
it will issue an error at generation time. Compare this to an ordinary name
which CMake will treat as a library assumed to be provided by the system if it
doesn’t know of a target by that name. This can lead to the error only becoming
apparent much later at build time.
add_executable(Main main.cpp)
add_library(Bar STATIC ...)
add_library(Foo::Bar ALIAS Bar)
# Typo in name being linked to, CMake will assume a
# library called "Bart" will be provided by the
# system at link time and won't issue an error.
target_link_libraries(Main PRIVATE Bart)
# Typo in name being linked to, CMake flags an error
# at generation time because a namespaced name must
# be a CMake target.
target_link_libraries(Main PRIVATE Foo::Bart)add_executable(Main main.cpp)
add_library(Bar STATIC ...)
add_library(Foo::Bar ALIAS Bar)
# Typo in name being linked to, CMake will assume a
# library called "Bart" will be provided by the
# system at link time and won't issue an error.
target_link_libraries(Main PRIVATE Bart)
# Typo in name being linked to, CMake flags an error
# at generation time because a namespaced name must
# be a CMake target.
target_link_libraries(Main PRIVATE Foo::Bart)
因此,链接到可用的命名空间名称会更加稳健。强烈鼓励项目至少为所有要安装/打包的目标定义命名空间别名。此类命名空间别名甚至可以在项目本身内使用,而不仅仅是由其他项目将其用作预构建的包或子项目。
第 26.3 节“安装导出”targetName还讨论了如何更改用于命名空间名称部分的名称
,这可以允许原始目标MyProj_Algo具有命名空间名称,MyProj::Algo而不是更冗长和重复的MyProj::MyProj_Algo.
It is therefore more robust to link to namespaced names where they are
available. Projects are strongly encouraged to define namespaced aliases at
least for all targets that are intended to be installed/packaged. Such
namespaced aliases can even be used within the project itself, not just by
other projects consuming it as a pre-built package or child project.
Section 26.3, “Installing Exports” also discusses how to change the name used for the
targetName part of a namespaced name, which can allow an original target
like MyProj_Algo to have a namespaced name like MyProj::Algo instead of
the more verbose and repetitive MyProj::MyProj_Algo.
当不使用GLOBAL关键字定义时,导入的目标仅在创建它们的目录范围内或以下可见。此行为源于其主要预期用途,即作为查找模块或包配置文件的一部分。查找模块或包配置文件定义的任何内容通常都应该具有本地可见性,因此它们通常不应该添加全局可见的目标。这允许项目层次结构的不同部分引入具有不同设置的相同包和模块,但不会相互干扰。
When defined without the GLOBAL keyword, imported targets are only visible in
the directory scope in which they are created or below. This behavior stems
from their main intended use, which is as part of a Find module or package
config file. Anything defined by a Find module or package config file is
generally expected to have local visibility, so they shouldn’t generally add
globally visible targets. This allows different parts of a project hierarchy to
pull in the same packages and modules with different settings, yet not
interfere with each other.
然而,在某些情况下,需要创建具有全局可见性的导入目标,例如确保在整个项目中一致使用特定包的相同版本或实例。在创建导入库时添加GLOBAL关键字可以实现此目的,但项目可能无法控制执行创建的命令。为了向项目提供解决这种情况的方法,CMake 3.11 引入了通过将目标的属性设置IMPORTED_GLOBAL为 true 来将导入的目标提升为全局可见性的功能。请注意,这是一种单向转换,不可能将全局目标降级回本地可见性。
Nevertheless, there are situations where imported targets need to be created
with global visibility, such as to ensure that the same version or instance of
a particular package is used consistently throughout the whole project. Adding
the GLOBAL keyword when creating the imported library achieves this, but the
project may not be in control of the command that does the creation. To provide
projects with a way to address this situation, CMake 3.11 introduced the
ability to promote an imported target to global visibility by setting the
target’s IMPORTED_GLOBAL property to true. Note that this is a one-way
transition, it is not possible to demote a global target back to local
visibility.
# Imported library created with local visibility.
# This could be in an external file brought in
# by an include() call rather than in the same
# file as the lines further below.
add_library(BuiltElsewhere STATIC IMPORTED)
set_target_properties(BuiltElsewhere PROPERTIES
IMPORTED_LOCATION /path/to/libSomething.a
)
# Promote the imported target to global visibility
set_target_properties(BuiltElsewhere PROPERTIES
IMPORTED_GLOBAL TRUE
)# Imported library created with local visibility.
# This could be in an external file brought in
# by an include() call rather than in the same
# file as the lines further below.
add_library(BuiltElsewhere STATIC IMPORTED)
set_target_properties(BuiltElsewhere PROPERTIES
IMPORTED_LOCATION /path/to/libSomething.a
)
# Promote the imported target to global visibility
set_target_properties(BuiltElsewhere PROPERTIES
IMPORTED_GLOBAL TRUE
)
需要注意的是,导入的目标只有在与促销完全相同的范围内定义时才能促销。无法升级在父作用域或子作用域中定义的导入目标。该include()命令不会引入新的目录范围,调用也不会引入,因此可以提升find_package()通过这种方式引入构建的文件定义的导入目标。事实上,这是创建促进导入目标的能力的主要用例。
It is important to note that an imported target can only be promoted if it is
defined in exactly the same scope as the promotion. An imported target defined
in a parent or child scope cannot be promoted. The include() command does not
introduce a new directory scope and neither does a find_package() call, so
imported targets defined by files brought into the build that way can be
promoted. In fact, this is the main use case for which the ability to promote
imported targets was created.
提升导入的目标不会提升任何已指向该目标的别名。导入目标的别名始终具有创建别名时导入目标的可见性。别名不支持提升全球知名度。
Promoting an imported target does not promote any aliases already pointing to that target. An alias to an imported target always has the visibility that the imported target had when the alias was created. Aliases do not support promotion to global visibility.
add_library(Original STATIC IMPORTED)
# Local alias (requires CMake 3.18 or later)
add_library(LocalAlias ALIAS Original)
# Promote imported target to global visibility,
# but LocalAlias remains with local visibility
set_target_properties(Original PROPERTIES
IMPORTED_GLOBAL TRUE
)
# Global alias (requires CMake 3.11 or later)
add_library(GlobalAlias ALIAS Original)add_library(Original STATIC IMPORTED)
# Local alias (requires CMake 3.18 or later)
add_library(LocalAlias ALIAS Original)
# Promote imported target to global visibility,
# but LocalAlias remains with local visibility
set_target_properties(Original PROPERTIES
IMPORTED_GLOBAL TRUE
)
# Global alias (requires CMake 3.11 or later)
add_library(GlobalAlias ALIAS Original)
实际上,很少需要导入目标的别名。
INTERFACE IMPORTED库在很大程度上可以实现相同的目标,并且它们适用于更广泛的 CMake 版本。
INTERFACE IMPORTED库不支持读取真实库目标的底层属性,但它们确实携带所有链接和传递属性。
In practice, aliases to imported targets should rarely be needed.
INTERFACE IMPORTED libraries can largely achieve the same thing and they work
for a wider range of CMake versions.
INTERFACE IMPORTED libraries don’t support reading the underlying properties
of the real library target, but they do carry all the linking and transitive
properties.
add_library(Original STATIC IMPORTED)
add_library(OtherName INTERFACE IMPORTED Original)
target_link_libraries(OtherName INTERFACE Original)add_library(Original STATIC IMPORTED)
add_library(OtherName INTERFACE IMPORTED Original)
target_link_libraries(OtherName INTERFACE Original)
库的另一个优点INTERFACE IMPORTED是,如果需要,可以将它们提升为全局可见性,而别名则不能。
An added advantage of INTERFACE IMPORTED libraries is that they can be
promoted to global visibility if required, whereas aliases cannot.
CMake 3.0 版本对项目管理目标之间的依赖关系和需求的推荐方式进行了重大更改。每个目标都能够在目录中携带所有必要的信息,而不是通过变量来指定大多数内容,然后必须由项目手动管理,或者通过目录级命令来应用到目录及以下目录中的所有目标,而没有太多区别。它自己的属性。这种焦点向以目标为中心的模型的转变也导致了一系列伪目标类型的出现,这些类型有助于更灵活、更准确地表达目标间关系。
Version 3.0 of CMake brought with it a significant change to the recommended way projects should manage dependencies and requirements between targets. Instead of specifying most things through variables which then had to be managed manually by the project, or by directory level commands that would apply to all targets in a directory and below without much discrimination, each target gained the ability to carry all the necessary information in its own properties. This shift in focus to a target-centric model has also led to a family of pseudo target types that facilitate expressing inter-target relationships more flexibly and accurately.
开发人员尤其应该熟悉接口库。他们开辟了一系列捕获和表达关系的技术,而无需创建或引用物理文件。它们对于表示仅标头库、资源集合和许多其他场景的详细信息非常有用,并且应该比尝试单独使用变量或目录级命令实现相同的结果更受青睐。
Developers should become familiar with interface libraries in particular. They open up a range of techniques for capturing and expressing relationships without needing to create or refer to a physical file. They can be useful for representing the details of header-only libraries, collections of resources and many other scenarios and should be strongly preferred over trying to achieve the same result with variables or directory-level commands alone.
一旦项目开始使用外部构建的包,或者它们引用通过查找模块找到的文件系统中的工具,就会经常遇到导入的目标。开发人员应该能够轻松地使用导入的目标,但通常不需要了解它们定义方式的所有细节,除非编写 Find 模块或手动为包创建配置文件。第 26 章“安装”中讨论了一些特定情况,开发人员可能会遇到导入目标的某些限制,但这种情况并不常见。
Imported targets are encountered frequently once projects start using externally built packages or they refer to tools from the file system that are found through Find modules. Developers should be comfortable using imported targets, but understanding all the ins and outs of how they are defined is not usually necessary unless writing Find modules or manually creating config files for a package. Some specific cases are discussed in Chapter 26, Installing where developers may come up against certain limitations of imported targets, but such scenarios are not very common.
许多较旧的 CMake 模块过去仅提供引用导入实体的变量。从 CMake 3.0 开始,这些模块正在逐步更新,以在适当的情况下提供导入的目标。对于项目需要引用外部工具或库的情况,最好通过导入的目标(如果可用)来实现。这些通常可以更好地抽象出平台差异、依赖于选项的工具选择等内容,但更重要的是,CMake 可以稳健地处理使用需求。如果可以选择使用导入库或变量来引用同一事物,则尽可能选择使用导入库。
A number of older CMake modules used to provide only variables to refer to imported entities. Starting with CMake 3.0, these modules are progressively being updated to also provide imported targets where appropriate. For those situations where a project needs to refer to an external tool or library, prefer to do so through an imported target if one is available. These typically do a better job of abstracting away things like platform differences, option-dependent tool selection and so on, but more importantly the usage requirements are then robustly handled by CMake. If there is a choice between using an imported library or a variable to refer to the same thing, prefer to use the imported library wherever possible.
优先定义静态库而不是对象库。静态库更简单,具有早期 CMake 版本更完整、更强大的支持,并且大多数开发人员都可以很好地理解它们。对象库有其用途,但它们也不如静态库灵活。特别是,在 CMake 3.12 之前,对象库根本无法链接,并且在 CMake 3.14 之前链接也不牢固。如果没有这样的链接,它们不支持传递依赖项,这迫使项目自己手动应用依赖项。这增加了出现错误和遗漏的机会。它还减少了库目标通常提供的封装。即使是名称本身也会引起开发人员的一些困惑,因为对象库不是真正的库,而只是一组未组合的对象文件,但开发人员有时仍然期望它的行为像真正的库一样。CMake 3.12 的变化模糊了这种区别,但剩余的差异仍然为意外结果留下了空间,与对象库相关的查询数量及其在 CMake 邮件列表和问题跟踪器上的传递行为就证明了这一点。
Prefer defining static libraries over object libraries. Static libraries are simpler, have more complete and robust support from earlier CMake versions and they are well understood by most developers. Object libraries have their uses, but they are also less flexible than static libraries. In particular, object libraries cannot be linked at all prior to CMake 3.12 and not robustly before CMake 3.14. Without such linking, they don’t support transitive dependencies, which forces projects to manually apply the dependencies themselves. This increases the opportunity for errors and omissions. It also reduces the encapsulation that a library target would normally provide. Even the name itself can cause some confusion among developers, since an object library is not a true library, but rather just a set of uncombined object files, yet developers still sometimes expect it to behave like a real library. The changes with CMake 3.12 blur that distinction, but the remaining differences still leave room for unexpected results, as evidenced by the number of queries relating to object libraries and their transitive behavior on the CMake mailing list and issue tracker.
在命名目标时,不要使用太通用的目标名称。全局可见的目标名称必须是唯一的,并且在较大的层次结构中使用时,名称可能会与其他项目的目标发生冲突。此外,请考虑namespace::…为项目非私有的每个目标(即可能最终安装或打包的每个目标)添加别名目标。这允许使用项目链接到命名空间目标名称而不是真实目标名称,这种技术使使用项目能够相对轻松地在构建子项目本身或使用预构建的安装项目之间切换。虽然这最初看起来像是额外的工作,但并没有多少收益,但它正在成为 CMake 社区中预期的标准实践,特别是对于那些需要花费大量时间来构建的项目。该模式将在第 26.3 节“安装导出”中进一步讨论。
When it comes to naming targets, don’t use target names that are too generic.
Globally visible target names must be unique and names may clash with targets
from other projects when used in a larger hierarchical arrangement. In
addition, consider adding an alias namespace::… target for each target that
is not private to the project (i.e. every target that may end up being
installed or packaged). This allows consuming projects to link to the
namespaced target name instead of the real target name, a technique which
enables consuming projects to switch between building the child project
themselves or using a pre-built installed project relatively easily. While this
may initially seem like extra work for not much gain, it is emerging as an
expected standard practice among the CMake community, especially for those
projects that take a non-trivial amount of time to build. This pattern is
discussed further in Section 26.3, “Installing Exports”.
不可避免地,在某些时候可能需要重命名或重构库,但可能存在期望现有库目标可链接到的外部项目。在这些情况下,请使用接口目标为重命名的目标提供旧名称,以便这些外部项目可以继续构建并在方便时进行更新。拆分库时,使用旧目标名称定义接口库,并让它定义新拆分库的链接依赖项。例如:
Inevitably, at some point it may become desirable to rename or refactor a library, but there may be external projects which expect the existing library targets to be available to link to. In these situations, use an interface target to provide an old name for a renamed target so that those external projects can continue to build and be updated at their convenience. When splitting up a library, define an interface library with the old target name and have it define link dependencies to the new split out libraries. For example:
# Old library previously defined like this:
add_library(DeepCompute SHARED ...)# Old library previously defined like this:
add_library(DeepCompute SHARED ...)
现在更改DeepCompute为INTERFACE链接到新重构库的库以保持向后兼容性:
Now change DeepCompute to an INTERFACE library that links to the new
refactored libraries to preserve backward compatibility:
# Now refactored into two separate libraries
add_library(ComputeAlgoA SHARED ...)
add_library(ComputeAlgoB SHARED ...)
# Forwarding interface library keeps old projects working
add_library(DeepCompute INTERFACE)
target_link_libraries(DeepCompute INTERFACE
ComputeAlgoA
ComputeAlgoB
)# Now refactored into two separate libraries
add_library(ComputeAlgoA SHARED ...)
add_library(ComputeAlgoB SHARED ...)
# Forwarding interface library keeps old projects working
add_library(DeepCompute INTERFACE)
target_link_libraries(DeepCompute INTERFACE
ComputeAlgoA
ComputeAlgoB
)
没有任何构建工具能够实现任何给定项目所需的所有功能。在某些时候,开发人员需要执行不属于直接支持的功能范围的任务。例如,可能需要运行特殊工具来生成源文件或在构建目标后对其进行后处理。可能需要复制、验证文件或计算哈希值。构建工件可能需要存档或联系通知服务。这些任务和其他任务并不总是符合可预测的模式,因此可以轻松地将它们作为通用构建系统功能提供。
No build tool can ever hope to implement every feature that will ever be needed by any given project. At some point, developers will need to carry out a task that falls outside the directly supported functionality. For example, a special tool may need to be run to produce source files or to post-process a target after it has been built. Files may need to be copied, verified or a hash value computed. Build artifacts may need to be archived or a notification service contacted. These and other tasks don’t always fit into a predictable pattern that allows them to be easily provided as a general build system capability.
CMake 通过自定义命令和自定义目标支持此类任务。这些允许在构建时执行任何命令或命令集,以执行项目所需的任何任意任务。CMake 还支持在配置时执行任务,从而启用依赖于在构建阶段之前甚至在处理当前文件的后续部分之前完成的任务的各种技术CMakeLists.txt。
CMake supports such tasks through custom commands and custom targets. These
allow any command or set of commands to be executed at build time to perform
whatever arbitrary tasks a project requires. CMake also supports executing
tasks at configure time, enabling various techniques that rely on tasks being
completed before the build stage or even before processing later parts of the
current CMakeLists.txt file.
库和可执行目标并不是 CMake 支持的唯一目标类型。项目还可以定义自己的自定义目标,执行任意任务,这些任务定义为要在构建时执行的命令序列。这些自定义目标是使用以下add_custom_target()命令定义的:
Library and executable targets are not the only kinds of targets CMake
supports. Projects can also define their own custom targets that perform
arbitrary tasks defined as a sequence of commands to be executed at build time.
These custom targets are defined using the add_custom_target() command:
add_custom_target(targetName
[ALL]
[command1 [args1...]]
[COMMAND command2 [args2...]]
[DEPENDS depends1...]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[VERBATIM]
[USES_TERMINAL] # Requires CMake 3.2 or later
[JOB_POOL poolName] # Requires CMake 3.15 or later
[SOURCES source1 [source2...]]
)add_custom_target(targetName
[ALL]
[command1 [args1...]]
[COMMAND command2 [args2...]]
[DEPENDS depends1...]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[VERBATIM]
[USES_TERMINAL] # Requires CMake 3.2 or later
[JOB_POOL poolName] # Requires CMake 3.15 or later
[SOURCES source1 [source2...]]
)
具有指定的新目标targetName将可用于构建,并且它将始终被认为是过时的。该ALL选项使all目标依赖于这个新的自定义目标(各个生成器对all目标的命名略有不同,但通常类似于all或ALL类似)。如果ALL未提供该选项,则仅在明确请求或构建依赖于它的其他目标时才构建目标。
A new target with the specified targetName will be available to the build
and it will always be considered out of date.
The ALL option makes the all target depend on this new custom target (the
various generators name the all target slightly differently, but it is
generally something like all, ALL or similar). If the ALL option is not
provided, then the target is only built if it is explicitly requested or
if building some other target that depends on it.
构建自定义目标时,将按照给定的顺序执行指定的命令,每个命令可以具有任意数量的参数。为了提高可读性,参数可以分为多行。第一个命令不需要COMMAND在其前面包含关键字,但为了清楚起见,建议COMMAND即使对于第一个命令也始终包含关键字。当指定多个命令时尤其如此,因为它使每个命令使用一致的形式。
When the custom target is built, the specified command(s) will be executed in
the order given, with each command able to have any number of arguments. For
improved readability, arguments can be split across multiple lines. The first
command does not need to have the COMMAND keyword preceding it, but for
clarity it is recommended to always include the COMMAND keyword even for the
first command. This is especially true when specifying multiple commands, since
it makes each command use a consistent form.
可以定义命令来执行可以在主机平台上执行的任何操作。典型的命令涉及运行脚本或系统提供的可执行文件,但它们也可以运行作为构建的一部分创建的可执行目标。如果另一个可执行目标名称被列为要执行的命令,CMake 将自动替换该其他目标可执行文件的构建位置。无论使用什么平台或 CMake 生成器,这都有效,从而使项目不必解决各种平台和生成器的差异,这些差异会导致一系列不同的输出目录结构、文件名等。如果需要使用另一个目标作为命令之一的参数,CMake 不会自动执行相同的替换,但使用
TARGET_FILE生成器表达式获得等效替换是微不足道的。项目应该利用这些功能,让 CMake 提供目标位置,而不是手动硬编码路径,因为这使得项目能够以最小的努力强大地支持所有平台和生成器类型。以下示例演示如何定义自定义目标,该目标使用其他两个目标作为命令和参数列表的一部分:
Commands can be defined to do anything that could be performed on the host
platform. Typical commands involve running a script or a system-provided
executable, but they can also run executable targets created as part of the
build. If another executable target name is listed as the command to execute,
CMake will automatically substitute the built location of that other target’s
executable. This works regardless of the platform or CMake generator being
used, thereby freeing the project from having to work out the various platform
and generator differences that lead to a range of different output directory
structures, file names, etc. If another target needs to be used as an argument
to one of the commands, CMake will not automatically perform the same
substitution, but it is trivial to obtain an equivalent substitution with the
TARGET_FILE generator expression. Projects should take advantage of these
features to let CMake provide locations of targets rather than hard-coding
paths manually, as this allows the project to robustly support all platforms
and generator types with minimal effort. The following example shows how to
define a custom target which uses two other targets as part of the command and
argument list:
add_executable(Hasher hasher.cpp)
add_library(MyLib api.cpp)
add_custom_target(CreateHash
COMMAND Hasher $<TARGET_FILE:MyLib>
)add_executable(Hasher hasher.cpp)
add_library(MyLib api.cpp)
add_custom_target(CreateHash
COMMAND Hasher $<TARGET_FILE:MyLib>
)
当目标用作要执行的命令时,CMake 会自动创建对该可执行目标的依赖项,以确保它在自定义目标之前构建。同样,如果在命令或其参数中的任何位置的以下生成器表达式之一中引用目标,也将自动在该目标上创建依赖关系:
When a target is used as the command to execute, CMake automatically creates a dependency on that executable target to ensure it is built before the custom target. Similarly, if a target is referred to in one of the following generator expressions anywhere in the command or its arguments, a dependency will also be automatically created on that target:
$<TARGET_FILE:…>
$<TARGET_FILE:…>
$<TARGET_LINKER_FILE:…>
$<TARGET_LINKER_FILE:…>
$<TARGET_SONAME_FILE:…>
$<TARGET_SONAME_FILE:…>
$<TARGET_PDB_FILE:…>
$<TARGET_PDB_FILE:…>
对于 CMake 3.18 及更早版本,其他$<TARGET_xxx:…>生成器表达式也会导致自动添加依赖项。对于 CMake 3.19 或更高版本,行为取决于策略 CMP0112。有关更多详细信息,请参阅该策略的 CMake 文档。要创建对此类生成器表达式中未提及的目标的依赖关系,请使用add_dependencies()命令定义该关系。
For CMake 3.18 and earlier, other $<TARGET_xxx:…> generator expressions
will also lead to a dependency being added automatically.
With CMake 3.19 or later, the behavior depends on policy CMP0112.
See the CMake documentation of that policy for further details.
To create a dependency on a target not mentioned in such generator
expressions, use the add_dependencies() command to define that
relationship.
如果依赖项存在于文件而不是目标上,则DEPENDS可以使用关键字直接指定该关系作为调用的一部分add_custom_target()
。请注意,DEPENDS不应用于目标依赖项,而只能用于文件依赖项。DEPENDS当列出的文件是由其他自定义命令生成时(请参阅
下面的第 18.3 节“生成文件的命令” ),该关键字特别有用,其中 CMake 将设置必要的依赖项以确保其他自定义命令在此自定义命令之前执行目标的命令。始终使用绝对路径DEPENDS,因为由于允许对多个位置进行路径匹配的旧功能,相对路径可能会产生意外结果。
If a dependency exists on a file rather than a target, the DEPENDS keyword
can be used to specify that relationship as part of the add_custom_target()
call directly. Note that DEPENDS should not be used for target dependencies,
only file dependencies. The DEPENDS keyword is especially useful when the
file being listed is generated by some other custom command (see
Section 18.3, “Commands That Generate Files” further below), where CMake will set up the
necessary dependencies to ensure the other custom commands execute before this
custom target’s commands. Always use an absolute path for DEPENDS, since
relative paths can give unexpected results due to a legacy feature that allows
path matching against multiple locations.
当提供多个命令时,每个命令都将按列出的顺序执行。但是,项目不应假定任何特定的 shell 行为,因为每个命令可能在其自己单独的 shell 中运行,或者根本不需要任何 shell 环境。自定义命令应该被定义为好像它们是独立执行的,并且没有任何 shell 功能,例如重定向、变量替换等,只强制执行命令顺序。虽然其中一些功能可能适用于某些平台,但并未得到普遍支持。此外,由于无法保证特定的 shell 行为,因此在不同平台上,可执行文件名称或其参数内的转义可能会有不同的处理方式。为了帮助减少这些差异,VERBATIM可以使用该选项来确保唯一完成的转义是由 CMake 本身在解析文件时进行的CMakeLists.txt。平台不会执行进一步的转义,因此开发人员可以对命令最终如何构建以供执行充满信心。如果有任何机会逃避相关性,VERBATIM建议使用关键字。
When multiple commands are provided, each one will be executed in the order
listed. A project should not assume any particular shell behavior, however, as
each command might run in its own separate shell or without any shell
environment at all. Custom commands should be defined as though they were being
executed in isolation and without any shell features such as redirection,
variable substitution, etc., with only command order being enforced. While some
of these features may work on some platforms, they are not universally
supported. Also, since no particular shell behavior is guaranteed, escaping
within the executable names or their arguments may be handled differently on
different platforms. To help reduce these differences, the VERBATIM option
can be used to ensure that the only escaping done is that by CMake itself when
parsing the CMakeLists.txt file. No further escaping is performed by the
platform, so the developer can have confidence in how the command is ultimately
constructed for execution. If there is any chance of escaping being relevant,
use of the VERBATIM keyword is recommended.
执行命令的目录默认为当前二进制目录。这可以使用WORKING_DIRECTORY选项进行更改,该选项可以是绝对路径或相对路径,后者相对于当前二进制目录。这意味着
${CMAKE_CURRENT_BINARY_DIR}不需要作为工作目录的一部分使用,因为相对路径已经暗示了它。
The directory in which the commands are executed is the current binary
directory by default. This can be changed with the WORKING_DIRECTORY option,
which can be an absolute path or a relative path, the latter being relative to
the current binary directory. This means that using
${CMAKE_CURRENT_BINARY_DIR} as part of the working directory should not be
necessary, since a relative path already implies it.
该BYPRODUCTS选项可用于列出在运行命令的过程中创建的其他文件。如果正在使用 Ninja 生成器,并且另一个目标依赖于作为运行这组自定义命令的副产品而创建的任何文件,则需要此选项。列出的文件BYPRODUCTS
被标记为GENERATED(对于所有生成器类型,而不仅仅是 Ninja),这确保构建工具知道如何正确处理与副产品文件相关的依赖项详细信息。对于自定义目标生成文件作为副产品的情况,请考虑是否add_custom_command()是定义命令及其输出内容的更合适的方法(请参阅
第 18.3 节,“生成文件的命令”)。
The BYPRODUCTS option can be used to list other files that are created as
part of running the command(s). If the Ninja generator is being used, this
option is required if another target depends on any of the files created as a
by-product of running this set of custom commands. Files listed as BYPRODUCTS
are marked as GENERATED (for all generator types, not just Ninja) which
ensures the build tool knows how to correctly handle dependency details related
to the by-product files. For cases where a custom target generates files as a
by-product, consider whether add_custom_command() would be a more appropriate
way to define the commands and the things it outputs (see
Section 18.3, “Commands That Generate Files”).
使用 CMake 3.20 或更高版本,支持一组受限的生成器表达式BYPRODUCTS。$<TARGET_FILE:…>不得使用任何涉及目标的表达式(例如)。
With CMake 3.20 or later, a restricted set of generator expressions is
supported for BYPRODUCTS.
Any expression that refers to a target (e.g. $<TARGET_FILE:…>) may not be
used.
如果命令在控制台上没有产生输出,有时使用该选项指定一条短消息会很有用COMMENT。指定的消息会在运行命令之前记录,因此如果命令由于某种原因默默失败,则注释可以作为有用的标记来指示构建失败的位置。但请注意,对于某些生成器,注释不会显示,因此这不能被视为可靠的机制,但对于那些支持它的生成器可能仍然有用。下面第 18.5 节“平台独立命令”中介绍了一种普遍支持的替代方案。
If the commands produce no output on the console, it can sometimes be useful to
specify a short message with the COMMENT option. The specified message is
logged just before running the commands, so if the commands silently fail for
some reason, the comment can be a useful marker to indicate where the build
failed. Note, however, that for some generators, the comment will not be shown,
so this cannot be considered a reliable mechanism, but it may still be useful
for those generators that do support it. A universally supported alternative is
presented in Section 18.5, “Platform Independent Commands” below.
USES_TERMINAL是另一个与控制台相关的选项,它指示 CMake 让命令直接访问终端(如果可能)。使用 Ninja 生成器时,这会产生将命令放入控制台池的效果。在某些情况下,这可能会带来更好的输出缓冲行为,例如帮助 IDE 环境更及时地捕获和呈现构建输出。如果非 IDE 构建需要交互式输入,它也很有用。USES_TERMINALCMake 3.2 及更高版本支持该选项。
USES_TERMINAL is another console-related option which instructs CMake to give
the command direct access to the terminal, if possible. When using the Ninja
generator, this has the effect of placing the command in the console pool. This
may lead to better output buffering behavior in some situations, such as
helping IDE environments capture and present the build output in a more timely
manner. It can also be useful if interactive input is required for non-IDE
builds. The USES_TERMINAL option is supported for CMake 3.2 and later.
为了提供对 Ninja 作业池的更多控制,CMake 3.15 添加了对该JOB_POOL选项的支持。虽然USES_TERMINAL将任务分配到控制台作业池,但该JOB_POOL
选项允许项目将任务分配到任何自定义作业池。
USES_TERMINAL并且JOB_POOL不能同时给出。工作池是一个相当高级的主题,很少有项目需要。有兴趣探索此主题的读者应查阅 CMake 文档,从JOB_POOLS全局属性开始。
To provide even more control over Ninja job pools, CMake 3.15 added support for
the JOB_POOL option.
Whereas USES_TERMINAL assigns the task to the console job pool, the JOB_POOL
option allows the project to assign the task to any custom job pool.
USES_TERMINAL and JOB_POOL cannot both be given.
Job pools are a fairly advanced topic that few projects should need.
Readers interested in exploring this topic should consult the CMake
documentation, starting with the JOB_POOLS global property.
该SOURCES选项允许列出任意文件,然后将其与自定义目标关联。这些文件可能被命令使用,也可能只是一些与目标松散关联的附加文件,例如文档等。列出文件SOURCES对构建或依赖关系没有影响,纯粹是为了将这些文件与目标关联起来的好处是 IDE 项目可以在适当的上下文中显示它们。有时,通过定义虚拟自定义目标并列出不带命令的源来利用此功能,只是为了使它们显示在 IDE 项目中。虽然这有效,但它确实有创建没有实际意义的构建目标的缺点。许多项目认为这是一种可以接受的权衡,而一些开发人员则认为这是不可取的,甚至是反模式。
The SOURCES option allows arbitrary files to be listed which will then be
associated with the custom target. These files might be used by the commands or
they could just be some additional files which are loosely associated with the
target, such as documentation, etc. Listing a file with SOURCES has no effect
on the build or the dependency relationships, it is purely for the benefit of
associating those files with the target so that IDE projects can show them in
an appropriate context. This feature is sometimes exploited by defining a dummy
custom target and listing sources with no commands just to make them show up in
IDE projects. While this works, it does have the disadvantage of creating a
build target with no real meaning. Many projects deem this to be an acceptable
trade-off, while some developers consider this undesirable or even an
anti-pattern.
自定义命令有时不需要定义新目标,它们可能会指定在构建现有目标时要执行的其他步骤。这是add_custom_command()应该与关键字一起使用的
地方TARGET,如下所示:
Custom commands sometimes do not require a new target to be defined, they may
instead specify additional steps to be performed when building an existing
target. This is where add_custom_command() should be used with the
TARGET keyword as follows:
add_custom_command(TARGET targetName buildStage
COMMAND command1 [args1...]
[COMMAND command2 [args2...]]
[WORKING_DIRECTORY dir]
[BYPRODUCTS files...]
[COMMENT comment]
[VERBATIM]
[USES_TERMINAL] # Requires CMake 3.2 or later
[JOB_POOL poolName] # Requires CMake 3.15 or later
)add_custom_command(TARGET targetName buildStage
COMMAND command1 [args1...]
[COMMAND command2 [args2...]]
[WORKING_DIRECTORY dir]
[BYPRODUCTS files...]
[COMMENT comment]
[VERBATIM]
[USES_TERMINAL] # Requires CMake 3.2 or later
[JOB_POOL poolName] # Requires CMake 3.15 or later
)
大多数选项与 的选项非常相似add_custom_target(),但上面的形式不是定义新目标,而是将命令附加到现有目标。该现有目标可以是可执行文件或库目标,甚至可以是自定义目标(有一些限制)。这些命令将作为构建的一部分执行targetName,参数buildStage必须为以下之一:
Most of the options are very similar to those for add_custom_target(), but
instead of defining a new target, the above form attaches the commands to an
existing target. That existing target can be an executable or library target,
or it can even be a custom target (with some restrictions). The commands will
be executed as part of building targetName, with the buildStage argument
required to be one of the following:
PRE_BUILD
PRE_BUILD
PRE_LINK替代。鉴于对此选项的支持有限,项目应瞄准不需要PRE_BUILD自定义命令的结构,以避免生成器之间的命令顺序差异。
PRE_LINK instead. Given the limited support for this option,
projects should aim for a structure which does not require a PRE_BUILD custom
command so as to avoid command ordering differences between generators.
PRE_LINK
PRE_LINK
PRE_LINK不支持。
PRE_LINK is not supported.
POST_BUILD
POST_BUILD
POST_BUILD任务相对常见,但PRE_LINK很少PRE_BUILD需要,因为通常可以通过使用替代OUTPUT形式
来避免它们add_custom_command()(请参阅下一节)。
POST_BUILD tasks are relatively common, but PRE_LINK and PRE_BUILD are
rarely needed since they can usually be avoided by using the OUTPUT form of
add_custom_command() instead (see the next section).
可以进行多次调用以add_custom_command()将多组自定义命令附加到特定目标。这可能很有用,例如,让某些命令从一个工作目录运行,而其他命令从其他地方运行。
Multiple calls to add_custom_command() can be made to append multiple sets of
custom commands to a particular target. This can be useful, for example, to
have some commands run from one working directory and other commands run from
somewhere else.
add_executable(MyExe main.cpp)
add_custom_command(TARGET MyExe POST_BUILD
COMMAND script1 $<TARGET_FILE:MyExe>
)
# Additional command which will run after the above
# from a different directory
add_custom_command(TARGET MyExe POST_BUILD
COMMAND writeHash $<TARGET_FILE:MyExe>
BYPRODUCTS ${CMAKE_BINARY_DIR}/verify/MyExe.md5
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/verify
)add_executable(MyExe main.cpp)
add_custom_command(TARGET MyExe POST_BUILD
COMMAND script1 $<TARGET_FILE:MyExe>
)
# Additional command which will run after the above
# from a different directory
add_custom_command(TARGET MyExe POST_BUILD
COMMAND writeHash $<TARGET_FILE:MyExe>
BYPRODUCTS ${CMAKE_BINARY_DIR}/verify/MyExe.md5
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/verify
)
将命令定义为目标的附加构建步骤涵盖了许多常见用例。然而,有时,项目需要通过运行一个命令或一系列命令来创建一个或多个文件,并且该文件的生成并不真正属于任何现有目标。这就是可以使用OUTPUT形式的
地方。add_custom_command()它实现了与表单相同的所有选项TARGET,以及一些与依赖性处理和附加到先前OUTPUT命令集相关的附加选项。
Defining commands as additional build steps for a target covers many common use
cases. Sometimes, however, a project needs to create one or more files by
running a command or series of commands and the generation of that file doesn’t
really belong to any existing target. This is where the OUTPUT form of
add_custom_command() can be used. It implements all of the same options
as the TARGET form as well as some additional options related to dependency
handling and appending to a previous OUTPUT command set.
add_custom_command(OUTPUT output1 [output2...]
COMMAND command1 [args1...]
[COMMAND command2 [args2...]]
[WORKING_DIRECTORY dir]
[BYPRODUCTS files...]
[COMMENT comment]
[VERBATIM]
[USES_TERMINAL] # Requires CMake 3.2 or later
[JOB_POOL poolName] # Requires CMake 3.15 or later
[APPEND]
[DEPENDS [depends1...]
[MAIN_DEPENDENCY depend]
[IMPLICIT_DEPENDS <lang1> depend1 [<lang2> depend2...]]
[DEPFILE depfile]
)add_custom_command(OUTPUT output1 [output2...]
COMMAND command1 [args1...]
[COMMAND command2 [args2...]]
[WORKING_DIRECTORY dir]
[BYPRODUCTS files...]
[COMMENT comment]
[VERBATIM]
[USES_TERMINAL] # Requires CMake 3.2 or later
[JOB_POOL poolName] # Requires CMake 3.15 or later
[APPEND]
[DEPENDS [depends1...]
[MAIN_DEPENDENCY depend]
[IMPLICIT_DEPENDS <lang1> depend1 [<lang2> depend2...]]
[DEPFILE depfile]
)
这种形式不需要指定目标和构建前/构建后阶段,而是需要在关键字后给出一个或多个输出文件名OUTPUT。然后,CMake 会将这些命令解释为生成指定输出文件的配方。如果指定输出文件时没有指定路径或指定了相对路径,则它们是相对于当前二进制目录的。在 CMake 3.20 或更高版本中,可以使用不引用目标的生成器表达式。
Instead of specifying a target and pre/post build stage, this form requires one
or more output file names to be given after the OUTPUT keyword. CMake will
then interpret the commands as a recipe for generating the named output files.
If the output files are specified with no path or with a relative path, they
are relative to the current binary directory.
With CMake 3.20 or later, generator expressions that do not refer to a target
can be used.
就其本身而言,这种形式不会导致生成输出文件,因为没有定义目标。但是,如果同一目录范围中定义的其他目标依赖于任何输出文件,CMake 将自动创建依赖关系,以确保输出文件在需要它们的目标之前生成。一个常见的错误是尝试使在不同目录范围中定义的目标依赖于 an 的输出add_custom_command(),但这是不受支持的。此外,只有一个目标应该依赖于任何输出文件,否则并行构建可能会尝试同时多次调用自定义命令以满足多个目标的依赖关系。
On its own, this form won’t result in the output files being built, since no
target is defined. If, however, some other target defined in the same directory
scope depends on any of the output files, CMake will automatically create
dependency relationships that ensure the output files are generated before the
target that needs them.
A common error is to try to make a target defined in a different directory
scope depend on the output of an add_custom_command(), but this is not
supported.
Furthermore, only one target should depend on any of the output files or else
parallel builds may try to invoke the custom command multiple times
simultaneously to satisfy the dependencies of multiple targets.
依赖于输出的目标add_custom_command()可以是普通的可执行文件、库目标,甚至可以是自定义目标。事实上,定义自定义目标很常见,只是为了为开发人员提供触发自定义命令的方法。上一节的哈希示例的以下变体演示了该技术:
The target that depends on the output of the add_custom_command() can be an
ordinary executable, a library target or it can even be a custom target.
In fact, it is quite common for a custom target to be defined simply to provide
a way for the developer to trigger the custom command.
The following variation on the hashing example of the preceding section
demonstrates the technique:
add_executable(MyExe main.cpp)
# Output file with relative path, generated in the
# build directory
add_custom_command(OUTPUT MyExe.md5
COMMAND writeHash $<TARGET_FILE:MyExe>
)
# Absolute path needed for DEPENDS, otherwise relative
# to source directory
add_custom_target(ComputeHash
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/MyExe.md5
)add_executable(MyExe main.cpp)
# Output file with relative path, generated in the
# build directory
add_custom_command(OUTPUT MyExe.md5
COMMAND writeHash $<TARGET_FILE:MyExe>
)
# Absolute path needed for DEPENDS, otherwise relative
# to source directory
add_custom_target(ComputeHash
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/MyExe.md5
)
以这种方式定义时,构建MyExe目标将不会导致运行哈希步骤,这与前面的示例(将哈希命令添加为MyExePOST_BUILD目标的步骤)不同。相反,只有当开发人员明确请求将其作为构建目标时,才会执行哈希。这允许在需要时定义和调用可选步骤,而不是始终运行,如果附加步骤非常耗时或并不总是相关,这将非常有用。
When defined this way, building the MyExe target will not result in running
the hashing step, unlike the earlier example which added the hashing command as
a POST_BUILD step of the MyExe target. Instead, hashing will only be
performed if the developer explicitly requests it as a build target. This
allows optional steps to be defined and invoked when needed instead of always
being run, which can be quite useful if the additional steps are time consuming
or won’t always be relevant.
当然,add_custom_command()也可以用于生成现有目标消耗的文件,例如生成源文件。在以下示例中,项目构建的可执行文件用于生成源文件,然后将其编译为另一个可执行文件的一部分。
Of course, add_custom_command() can also be used to generate files consumed
by existing targets, such as generating source files. In the following example,
an executable built by the project is used to generate a source file which is
then compiled as part of another executable.
add_executable(Generator generator.cpp)
add_custom_command(OUTPUT onTheFly.cpp
COMMAND Generator
)
add_executable(MyExe
${CMAKE_CURRENT_BINARY_DIR}/onTheFly.cpp
)add_executable(Generator generator.cpp)
add_custom_command(OUTPUT onTheFly.cpp
COMMAND Generator
)
add_executable(MyExe
${CMAKE_CURRENT_BINARY_DIR}/onTheFly.cpp
)
CMake 会自动识别MyExe需要自定义命令生成的源文件,而这又需要Generator可执行文件。要求MyExe构建目标将导致在Generator构建之前构建生成的源文件MyExe。但请注意,这种依赖关系是有局限性的。考虑以下场景:
CMake automatically recognizes that MyExe needs the source file generated by
the custom command, which in turn requires the Generator executable. Asking
for the MyExe target to be built will result in the Generator and the
generated source file being built before building MyExe. Note, however, that
this dependency relationship has limitations. Consider the following scenario:
onTheFly.cpp文件最初不存在。
onTheFly.cpp file initially does not exist.
MyExe目标,这会产生以下序列:
Generator已更新。
onTheFly.cpp.
MyExe已建立。
MyExe target, which results in the following sequence:
Generator target is brought up to date.
onTheFly.cpp.
MyExe target is built.
generator.cpp文件。
generator.cpp file.
MyExe目标,这次会产生以下序列:
Generator已更新。这将导致
Generator可执行文件被重建,因为它的源文件被修改了。
onTheFly.cpp已经存在。
MyExe不会重建,因为它的源文件保持不变。
MyExe target again, which this time results in the following
sequence:
Generator target is brought up to date. This will cause the
Generator executable to be rebuilt because its source file was
modified.
onTheFly.cpp already exists.
MyExe target is NOT rebuilt because its source file remains
unchanged.
人们可能直观地期望,如果Generator目标被重建,那么自定义命令也应该重新运行。CMake 自动创建的依赖项不会强制执行此操作,它会创建一个较弱的依赖项,这确实确保Generator更新,但仅在输出文件完全丢失时才运行自定义命令。为了在Generator重建目标时强制重新运行自定义命令,必须指定显式依赖项,而不是依赖 CMake 自动创建的依赖项。
One might intuitively expect that if the Generator target is rebuilt, then
the custom command should also be re-run. The dependency CMake automatically
creates does not enforce this, it creates a weaker dependency which
does ensure Generator is brought up to date but the custom command is only
run if the output file is missing altogether. In order to force the custom
command to be re-run if the Generator target is rebuilt, an explicit
dependency has to be specified rather than relying on the dependency CMake
automatically creates.
可以使用该DEPENDS选项手动指定依赖关系。列出的项目DEPENDS可以是 CMake 目标或文件(将此与只能列出文件的DEPENDS
选项进行比较)。add_custom_target()如果列出了目标,则每当需要更新自定义命令的输出文件时,该目标都会更新。同样,如果修改了列出的文件,并且需要任何自定义命令的输出文件,则将执行自定义命令。此外,如果任何列出的文件本身是同一目录范围内另一个自定义命令的输出文件,则将首先执行该其他自定义命令。至于add_custom_target(),如果列出文件 ,请始终使用绝对路径,以DEPENDS避免出现不明确的遗留行为。
Dependencies can be manually specified with the DEPENDS option. Items listed
with DEPENDS can be CMake targets or files (compare this with the DEPENDS
option for add_custom_target() which can only list files). If a target is
listed, it will be brought up to date any time the custom command’s output
files are required to be brought up to date. Similarly, if a listed file is
modified, the custom command will be executed if anything requires any of the
custom command’s output files. Furthermore, if any listed file is itself an
output file of another custom command in the same directory scope, that other
custom command will be executed first. As for add_custom_target(), always use
an absolute path if listing a file for DEPENDS to avoid ambiguous legacy
behavior.
虽然 CMake 的自动依赖关系可能看起来很方便,但实际上,项目通常仍然需要在一个部分中列出所有必需的目标和文件,DEPENDS以确保充分指定完整的依赖关系。很容易DEPENDS错误地省略该部分,因为第一个构建将运行自定义命令来创建丢失的输出文件,并且构建看起来运行正常。除非删除输出文件,否则后续构建不会重新运行自定义命令,即使重建任何自动检测到的依赖项目标也是如此。这很容易被忽视,在复杂的项目中通常很长一段时间都未被发现,直到开发人员遇到这种情况并试图找出为什么某些东西没有按预期重建的原因。因此,开发人员应该预期DEPENDS通常需要一个部分,除非自定义命令不需要构建或任何项目源文件创建的任何内容。
While CMake’s automatic dependencies may seem convenient, in practice the
project will still typically need to list out all the required targets and
files in a DEPENDS section to ensure that the full dependency relationships
are adequately specified. It can be easy to omit the DEPENDS section by
mistake, since the first build will run the custom command to create the
missing output files and the build will appear to be behaving correctly.
Subsequent builds will not re-run the custom command unless the output file is
removed, even if any of the automatically detected dependency targets are
rebuilt. This can be easy to miss, often going undetected for a long time in
complex projects until a developer encounters the situation and tries to work
out why something isn’t being rebuilt when it was expected to be. Therefore,
developers should expect that a DEPENDS section will typically be needed
unless the custom command doesn’t require anything created by the build or
any of the project’s source files.
另一个常见错误是不创建对自定义命令所需的文件的依赖关系,但该文件未作为要执行的命令行的一部分列出。此类文件需要出现在某个DEPENDS部分中,构建才会被认为是稳健的。
Another common error is to not create a dependency on a file that is needed by
the custom command, but which isn’t listed as part of the command line to be
executed. Such files need to appear in a DEPENDS section for the build to be
considered robust.
. 还支持一些更多与依赖项相关的选项
add_custom_command()。该MAIN_DEPENDENCY选项旨在标识应被视为自定义命令的主要依赖项的源文件。它与列出的文件具有基本相同的效果DEPENDS,但某些生成器可能会应用附加逻辑,例如将自定义命令放置在 IDE 项目中的位置。需要注意的一个重要区别是,如果源文件被列为MAIN_DEPENDENCY,则自定义命令将替代该源文件通常的编译方式。这可能会导致一些意想不到的结果。考虑以下示例:
There are a few more dependency-related options supported by
add_custom_command(). The MAIN_DEPENDENCY option is intended to identify a
source file which should be considered the main dependency of the custom
command. It has mostly the same effect as DEPENDS for the listed file, but
some generators may apply additional logic such as where to place the custom
command in an IDE project. An important distinction to note is that if a source
file is listed as a MAIN_DEPENDENCY, then the custom command becomes a
replacement for how that source file would normally be compiled. This can lead
to some unexpected results. Consider the following example:
add_custom_command(OUTPUT transformed.cpp
COMMAND transform
${CMAKE_CURRENT_SOURCE_DIR}/original.cpp
transformed.cpp
MAIN_DEPENDENCY
${CMAKE_CURRENT_SOURCE_DIR}/original.cpp
)
add_executable(Original original.cpp)
add_executable(Transformed transformed.cpp)add_custom_command(OUTPUT transformed.cpp
COMMAND transform
${CMAKE_CURRENT_SOURCE_DIR}/original.cpp
transformed.cpp
MAIN_DEPENDENCY
${CMAKE_CURRENT_SOURCE_DIR}/original.cpp
)
add_executable(Original original.cpp)
add_executable(Transformed transformed.cpp)
上面的内容将导致Original目标的链接器错误,因为
original.cpp不会被编译为目标文件,因此根本没有目标文件(因此没有main()函数)。相反,构建工具会将original.cpp其视为用于创建
transformed.cpp. DEPENDS可以通过使用代替
来解决该问题MAIN_DEPENDENCY,因为这将保留相同的依赖关系,但不会导致original.cpp
源文件的默认编译规则被替换。
The above would lead to a linker error for the Original target because
original.cpp would not be compiled to an object file, so there would be no
object files at all (and therefore no main() function). Instead, the build
tool would treat original.cpp as an input file used to create
transformed.cpp. The problem can be fixed by using DEPENDS instead of
MAIN_DEPENDENCY, as this would preserve the same dependency relationship, but
it would not result in the default compilation rule for the original.cpp
source file being replaced.
其他两个与依赖项相关的选项IMPLICIT_DEPENDS和DEPFILE并非所有项目生成器都普遍支持。
IMPLICIT_DEPENDS指示 CMake 调用 C 或 C++ 扫描器来确定列出的文件的依赖关系。除 Makefile 生成器之外的所有生成器都会忽略它,因此如果有其他替代方案可用于表达必要的依赖项,项目通常应避免使用它。
DEPFILE可用于提供*.d依赖文件(项目负责生成),但直到 CMake 3.19 为止,只有 Ninja 生成器支持它。从 CMake 3.20 开始,DEPFILE还可以与 Makefile 生成器一起使用,而 CMake 3.21 添加了对 Xcode 和 Visual Studio 生成器的支持。虽然 depfile 有其用途,但它们使用起来更加复杂,并且对于大多数典型项目来说不需要手动管理。
IMPLICIT_DEPENDS并且DEPFILE不能一起使用。
The other two dependency-related options, IMPLICIT_DEPENDS and DEPFILE, are
not universally supported by all project generators.
IMPLICIT_DEPENDS directs CMake to invoke a C or C++ scanner to determine
dependencies of the listed files.
It is ignored for all but Makefile generators, so projects should generally
avoid it if other alternatives are available for expressing the necessary
dependencies.
DEPFILE can be used to provide a *.d dependency file (which the project is
responsible for generating), but up until CMake 3.19, only the Ninja generator
supported it.
From CMake 3.20, DEPFILE can also be used with Makefile generators, while
CMake 3.21 added support for the Xcode and Visual Studio generators.
While depfiles have their uses, they are more complex to work with and they
shouldn’t need to be manually managed for most typical projects.
IMPLICIT_DEPENDS and DEPFILE cannot be used together.
CMake 3.20 还引入了另一项更改,DEPFILE该更改可能会影响在早期 CMake 版本中使用该功能的项目。CMake 3.20 添加了策略CMP0116,与大多数策略不同,即使项目没有调用或依赖该OLD
行为,也可能会导致警告。该警告提醒您注意,当
与对顶级源目录以外的任何位置的DEPFILE调用一起使用时,相对路径的处理方式发生了变化。add_custom_command()CMake 无法可靠地检查 depfile 的内容,因为它可以并且通常在构建时更新。因此,除非项目已更新以确保策略CMP0116设置为,否则它会保守地发出警告NEW。应检查使用DEPFILECMake 3.19 或更早版本的项目,以确保按照
CMP0116策略要求使用绝对路径。然后可以更新它们以设置策略以避免警告。这可以通过调整传递给调用的版本范围来全局完成
cmake_minimum_required(),也可以在相关调用周围本地完成。
CMake 3.20 also introduced another change related to DEPFILE which may
affect projects that were using that functionality with earlier CMake
versions.
CMake 3.20 added policy CMP0116 which, unlike most policies, can result in
warnings even where the project is not invoking or relying on the OLD
behavior.
The warning draws attention to the changed handling of relative paths when
DEPFILE is used with a call to add_custom_command() anywhere other than in
the top level source directory.
CMake cannot reliably check the contents of a depfile, since it can be and
usually is updated at build time.
Therefore, it conservatively issues a warning unless the project has been
updated to ensure that policy CMP0116 is set to NEW.
Projects that were using DEPFILE with CMake 3.19 or earlier should be
checked to ensure that absolute paths are used in accordance with the
CMP0116 policy requirements.
They can then be updated to set the policy to avoid the warning.
This can be done globally by adjusting the version range passed to the
cmake_minimum_required() call, or locally around the call in question.
以下示例演示了如何在本地调整策略设置而不影响最低 CMake 版本要求(有关更多详细信息,请参阅第 12 章,策略):
The following example demonstrates how to adjust the policy setting locally without affecting the minimum CMake version requirement (see Chapter 12, Policies for further details):
# Give ourselves a local policy set we can safely modify
cmake_policy(PUSH)
# Use the NEW policy setting only if it is available
if(POLICY CMP0116)
cmake_policy(SET CMP0116 NEW)
endif()
# We guarantee that depfile.d will not use relative paths
# for any dependency it specifies
add_custom_command(OUTPUT ...
DEPFILE /some/absolute/path/to/depfile.d
...
)
# Restore the original policy settings
cmake_policy(POP)# Give ourselves a local policy set we can safely modify
cmake_policy(PUSH)
# Use the NEW policy setting only if it is available
if(POLICY CMP0116)
cmake_policy(SET CMP0116 NEW)
endif()
# We guarantee that depfile.d will not use relative paths
# for any dependency it specifies
add_custom_command(OUTPUT ...
DEPFILE /some/absolute/path/to/depfile.d
...
)
# Restore the original policy settings
cmake_policy(POP)
当将更多依赖项或命令附加到同一输出文件或目标时,OUTPUT和形式的行为也略有不同。TARGET对于OUTPUT表单,APPEND必须指定关键字,并且
OUTPUT首次和后续调用时列出的第一个文件必须相同
add_custom_command()。仅COMMAND和DEPENDS可用于对同一输出文件的第二次及后续调用。当关键字存在时,其他选项(例如MAIN_DEPENDENCY、WORKING_DIRECTORY和
COMMENT)将被忽略。APPEND相反,对于该TARGET表单,对于同一目标的APPEND第二次和后续调用不需要关键字。还可以为每个调用指定和add_custom_command()选项,它们将对在该调用中添加的命令生效。COMMENTWORKING_DIRECTORY
The OUTPUT and TARGET forms also have slightly different behavior when it
comes to appending more dependencies or commands to the same output file or
target.
For the OUTPUT form, the APPEND keyword must be specified and the first
OUTPUT file listed must be the same for the first and subsequent calls to
add_custom_command().
Only COMMAND and DEPENDS can be used for the second and subsequent calls
for the same output file.
The other options, such as MAIN_DEPENDENCY, WORKING_DIRECTORY and
COMMENT, are ignored when the APPEND keyword is present.
In contrast, for the TARGET form, no APPEND keyword is necessary for
second and subsequent calls to add_custom_command() for the same target.
The COMMENT and WORKING_DIRECTORY options can also be specified for each
call and they will take effect for the commands being added in that call.
和add_custom_target()都add_custom_command()定义了在构建阶段要执行的命令。这通常是应该运行自定义命令的时候,但在某些情况下需要在配置阶段执行自定义任务。何时需要这样做的一些示例包括:
Both add_custom_target() and add_custom_command() define commands to be
executed during the build stage.
This is typically when custom commands should be run, but there are some
situations where a custom task needs to be performed during the configure
stage instead.
Some examples of when this is needed include:
CMakeLists.txt需要作为当前配置步骤的一部分包含或处理的文件或其他文件。
CMakeLists.txt or other files which need to be included or
processed as part of the current configure step.
CMake 提供了execute_process()在配置阶段运行此类任务的命令:
CMake provides the execute_process() command for running tasks like these
during the configure stage:
execute_process(
COMMAND command1 [args1...]
[COMMAND command2 [args2...]]
[WORKING_DIRECTORY directory]
[RESULT_VARIABLE resultVar]
[RESULTS_VARIABLE resultsVar]
[OUTPUT_VARIABLE outputVar]
[ERROR_VARIABLE errorVar]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE]
[INPUT_FILE inFile]
[OUTPUT_FILE outFile]
[ERROR_FILE errorFile]
[OUTPUT_QUIET]
[ERROR_QUIET]
[TIMEOUT seconds]
# CMake 3.15 or later required:
[COMMAND_ECHO STDOUT | STDERR | NONE]
# CMake 3.18 or later required:
[ECHO_OUTPUT_VARIABLE]
[ECHO_ERROR_VARIABLE]
# CMake 3.19 or later required:
[COMMAND_ERROR_IS_FATAL ANY | LAST]
)execute_process(
COMMAND command1 [args1...]
[COMMAND command2 [args2...]]
[WORKING_DIRECTORY directory]
[RESULT_VARIABLE resultVar]
[RESULTS_VARIABLE resultsVar]
[OUTPUT_VARIABLE outputVar]
[ERROR_VARIABLE errorVar]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE]
[INPUT_FILE inFile]
[OUTPUT_FILE outFile]
[ERROR_FILE errorFile]
[OUTPUT_QUIET]
[ERROR_QUIET]
[TIMEOUT seconds]
# CMake 3.15 or later required:
[COMMAND_ECHO STDOUT | STDERR | NONE]
# CMake 3.18 or later required:
[ECHO_OUTPUT_VARIABLE]
[ECHO_ERROR_VARIABLE]
# CMake 3.19 or later required:
[COMMAND_ERROR_IS_FATAL ANY | LAST]
)
add_custom_command()与和类似add_custom_target(),一个或多个
COMMAND部分指定要执行的任务,并且该WORKING_DIRECTORY
选项可用于控制这些命令的运行位置。命令按原样传递到操作系统执行,无需中间 shell 环境。因此,不支持输入/输出重定向和环境变量等功能。命令立即运行。
Similar to add_custom_command() and add_custom_target(), one or more
COMMAND sections specify the tasks to be executed and the WORKING_DIRECTORY
option can be used to control where those commands are run. The commands are
passed to the operating system for execution as is with no intermediate shell
environment. Therefore, features like input/output redirection and environment
variables are not supported. The commands run immediately.
如果给出多个命令,它们将按顺序执行,但不是彼此完全独立,而是一个命令的标准输出通过管道传输到下一个命令的输入。在没有任何其他选项的情况下,最后一个命令的输出将发送到 CMake 进程本身的输出,但每个命令的标准错误将发送到 CMake 进程的标准错误流。
If multiple commands are given, they are executed in order, but instead of being fully independent from each other, the standard output from one command is piped to the input of the next. In the absence of any other options, the output of the last command is sent to the output of the CMake process itself but the standard error of every command is sent to the standard error stream of the CMake process.
可以捕获标准输出和标准错误流并将其存储在变量中,而不是发送到默认管道。可以通过使用选项指定要存储它的变量名称来捕获命令集中最后一个命令的输出OUTPUT_VARIABLE。同样,所有命令的标准错误流都可以存储在该ERROR_VARIABLE选项命名的变量中。将相同的变量名称传递给这两个选项将导致标准输出和标准错误被合并,就像输出到终端一样,合并的结果存储在命名变量中。使用 CMake 3.18 或更高版本,可以添加ECHO_OUTPUT_VARIABLE和ECHO_ERROR_VARIABLE
选项来回显输出和错误流,同时还将它们捕获到变量中。这对于长时间运行的命令很有用,在输出中查看进度有助于确认命令尚未挂起。
The standard output and standard error streams can be captured and stored in
variables instead of being sent to the default pipes. The output of the last
command in the set of commands can be captured by specifying the name of a
variable to store it in with the OUTPUT_VARIABLE option. Similarly, the
standard error streams of all commands can be stored in the variable named by
the ERROR_VARIABLE option. Passing the same variable name to both of these
options will result in the standard output and standard error being merged just
as they would be if outputting to a terminal, with the merged result being
stored in the named variable.
With CMake 3.18 or later, the ECHO_OUTPUT_VARIABLE and ECHO_ERROR_VARIABLE
options can be added to echo the output and error streams while also capturing
them to variables.
This can be useful for long-running commands where seeing progress in the
output helps to confirm that the command has not hung.
如果OUTPUT_STRIP_TRAILING_WHITESPACE存在该选项,则存储在输出变量中的内容中将省略任何尾随空格。该ERROR_STRIP_TRAILING_WHITESPACE选项对存储在错误变量中的内容执行类似的操作。如果使用输出或错误变量的内容进行任何类型的字符串比较,一个常见的问题是无法考虑尾随空格,因此通常需要将其删除。
If the OUTPUT_STRIP_TRAILING_WHITESPACE option is present, any trailing
whitespace will be omitted from the content stored in the output variable.
The ERROR_STRIP_TRAILING_WHITESPACE option does a similar thing for the
content stored in the error variable.
If using the output or error variables’ contents for any sort of string
comparison, a common problem is failing to account for trailing whitespace, so
its removal is often desirable.
可以将它们发送到文件,而不是捕获变量中的输出和错误流。和选项可用于指定要将流发送到的文件的名称OUTPUT_FILE。ERROR_FILE就像以变量为中心的选项一样,为两个结果指定相同的文件名会产生合并流。另外,可以使用该选项为第一个命令的输入流指定一个文件INPUT_FILE。但请注意,
OUTPUT_STRIP_TRAILING_WHITESPACE和ERROR_STRIP_TRAILING_WHITESPACE
选项对发送到文件的内容没有影响。捕获到文件时也无法回显输出或错误流。
Instead of capturing the output and error streams in variables, they can be
sent to files. The OUTPUT_FILE and ERROR_FILE options can be used to
specify the names of files to send the streams to. Just like the
variable-focused options, specifying the same file name for both results in a
merged stream. In addition, a file can be specified for the input stream to the
first command with the INPUT_FILE option. Note, however, that the
OUTPUT_STRIP_TRAILING_WHITESPACE and ERROR_STRIP_TRAILING_WHITESPACE
options have no effect on content sent to files.
There is also no ability to echo the output or error streams when capturing to
files.
同一流不能在变量中捕获并同时发送到文件。但是,可以将不同的流发送到不同的位置,例如将输出流发送到变量,将错误流发送到文件,反之亦然。也可以使用OUTPUT_QUIET和ERROR_QUIET选项默默地丢弃流的内容。如果只关心命令的成功或失败,这些选项可能很有用。
The same stream cannot be captured in a variable and sent to a file at the same
time. It is possible, however, to send different streams to different places,
such as the output stream to a variable and the error stream to a file or vice
versa. It is also possible to silently discard the content of a stream
altogether with the OUTPUT_QUIET and ERROR_QUIET options. These options can
be useful if just success or failure of a command is of interest.
可以使用该选项捕获命令集的成功或失败
RESULT_VARIABLE。运行命令的结果将存储在命名变量中,作为最后一个命令的整数返回代码或包含某种错误消息的字符串。该if()命令方便地将非空错误字符串和除 0 之外的整数值视为布尔 true(除非项目很不幸有一个满足特殊情况之一的错误字符串,请参阅第6.1.1 节“基本表达式”) 。因此,检查调用是否成功execute_process()通常相对简单:
Success or failure of the set of commands can be captured using the
RESULT_VARIABLE option. The result of running the commands will be stored in
the named variable as either an integer return code of the last command or a
string containing some kind of error message. The if() command conveniently
treats both non-empty error strings and integer values other than 0 as boolean
true (unless a project is unlucky enough to have an error string that satisfies
one of the special cases, see Section 6.1.1, “Basic Expressions”). Therefore, checking for
the success of a call to execute_process() is generally relatively simple:
execute_process(
COMMAND runSomeScript
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR "runSomeScript failed: ${result}")
endif()execute_process(
COMMAND runSomeScript
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR "runSomeScript failed: ${result}")
endif()
从 CMake 3.10 开始,如果需要每个命令的结果而不仅仅是最后一个命令的结果,则RESULTS_VARIABLE可以使用该选项。此选项将每个命令的结果存储在名为 by 的变量中
resultsVar作为列表。
From CMake 3.10, if the result of each individual command is required rather
than just the last one, the RESULTS_VARIABLE option can be used instead. This
option stores the result of each command in the variable named by
resultsVar as a list.
在 CMake 3.19 或更高版本中,COMMAND_ERROR_IS_FATAL如果命令失败,该选项可以用作更简洁的方式来停止并出现错误。它避免了接收变量中的结果并执行显式检查的需要,因为检查是由命令execute_process()直接执行的。该选项后面必须跟有ANY或LAST。COMMAND当给出多个命令时,如果任何命令失败,则指定ANY将导致
命令失败并出现致命错误。execute_process()如果LAST改为给出,则execute_process()仅当最后一个
COMMAND失败时才会失败。如果只COMMAND给出一个,则ANY和LAST是等价的。
With CMake 3.19 or later, the COMMAND_ERROR_IS_FATAL option can be used as a
more concise way of halting with an error if the command fails.
It avoids the need to receive the result in a variable and perform an explicit
check, since the check is performed by the execute_process() command
directly.
The option must be followed by either ANY or LAST.
When more than one COMMAND is given, specifying ANY will cause the
execute_process() command to fail with a fatal error if any of the commands
fail.
If LAST is given instead, then execute_process() only fails if the last
COMMAND fails.
If only one COMMAND is given, then ANY and LAST are equivalent.
# Automatically halt with an error if either command fails
execute_process(
COMMAND runSomeScript
COMMAND runSomethingElse
COMMAND_ERROR_IS_FATAL ANY
)# Automatically halt with an error if either command fails
execute_process(
COMMAND runSomeScript
COMMAND runSomethingElse
COMMAND_ERROR_IS_FATAL ANY
)
该TIMEOUT选项可用于处理运行时间可能比预期长或可能永远无法完成的命令。这可确保配置步骤不会无限期地阻塞,并允许将意外长的配置步骤视为错误。但请注意,该TIMEOUT选项本身不会导致 CMake 停止并报告错误。RESULT_VARIABLE仍然有必要使用测试来检查结果if(),或者提供COMMAND_ERROR_IS_FATAL选项。请注意,在最初的 CMake 3.19.0 版本中,如果命令超时,该COMMAND_ERROR_IS_FATAL选项不会捕获该命令并将其视为致命错误。该问题已在 CMake 3.19.2 中修复,因此如果使用此选项,请将此视为最低 CMake 版本。如果使用该RESULT_VARIABLE方法,结果变量将保存一个错误字符串,指示命令由于运行时间太长而因超时而终止,因此在错误消息中打印它是有用的。
The TIMEOUT option can be used to handle commands which may run longer than
expected or which might possibly never complete.
This ensures the configure step doesn’t block indefinitely and allows an
unexpectedly long configure step to be treated as an error.
Note, however, that the TIMEOUT option on its own won’t cause CMake to halt
and report an error.
It is still necessary to either check the result using RESULT_VARIABLE and
an if() test, or provide the COMMAND_ERROR_IS_FATAL option.
Note that in the initial CMake 3.19.0 release, if a command timed out, it was
not caught and treated as a fatal error by the COMMAND_ERROR_IS_FATAL option.
That was fixed in CMake 3.19.2, so consider this to be the minimum CMake
version if using this option.
If using the RESULT_VARIABLE method, the result variable will hold an error
string indicating the command was terminated due to timeout if it runs for too
long, so printing it in the error message is useful.
CMake 3.15 添加了对该COMMAND_ECHO选项的支持,该选项后面必须跟有STDOUT,STDERR或 之一NONE。这控制回显每个命令的位置COMMAND(命令行本身,而不是命令的输出),或者在 的情况下NONE,防止回显命令。如果该COMMAND_ECHO选项不存在,则默认行为由CMAKE_EXECUTE_PROCESS_COMMAND_ECHO变量确定,该变量支持相同的三个值。如果未定义该变量或者 CMake 版本为 3.14 或更早版本,则不会回显命令。
CMake 3.15 added support for the COMMAND_ECHO option, which must be followed
by one of STDOUT, STDERR or NONE.
This controls where to echo each COMMAND (the command line itself, not the
command’s output), or in the case of NONE, prevents commands from being
echoed.
If the COMMAND_ECHO option is not present, the default behavior is determined
by the CMAKE_EXECUTE_PROCESS_COMMAND_ECHO variable, which supports the
same three values.
If that variable isn’t defined either or the CMake version is 3.14 or earlier,
commands are not echoed.
当 CMake 执行命令时,子进程很大程度上继承了与主进程相同的环境。一个重要的例外是,第一次在项目上运行 CMake 时,子进程的CC和CXX环境变量会显式设置为主构建使用的 C 和 C++ 编译器(如果主项目启用了 C 和 C++ 编译器) C++ 语言)。对于后续的 CMake 运行,CC和CXX环境变量不会
以这种方式替换,如果命令执行的操作依赖于CC和/或每次调用CXX时都具有相同的值,则可能会导致意外结果。execute_process()这种未记录的行为自 CMake 的早期版本以来就已经存在,甚至可以追溯到现在已弃用的
exec_program()命令execute_process()。添加它是为了方便子进程能够使用与主项目相同的编译器来配置和运行子构建。然而,在某些情况下,子进程可能不希望保留编译器,例如当主构建进行交叉编译但子进程应使用默认主机编译器时。CMAKE_GENERATOR_NO_COMPILER_ENV在这种情况下,项目可以将名为布尔真值的变量设置
为布尔真值,然后 CMake 将不会CC设置CXX任何execute_process()调用,甚至是初始调用。
When CMake executes the commands, the child process largely inherits the same
environment as the main process. An important exception to this is that the
first time CMake is run on a project, the CC and CXX environment variables
of the child process are explicitly set to the C and C++ compilers being used
by the main build (if the main project has enabled the C and C++ languages).
For subsequent CMake runs, the CC and CXX environment variables are not
substituted in this way, which can lead to unexpected results if the commands
perform actions that rely on CC and/or CXX having the same values every
time execute_process() is called. This undocumented behavior has existed
since early versions of CMake, even as far back as the now deprecated
exec_program() command which execute_process() replaced. It was added to
facilitate child processes being able to configure and run sub-builds with the
same compilers as the main project. In some cases, however, the child process
might not want the compiler to be preserved, such as when the main build is
cross-compiling but the child process should use the default host compilers. In
such cases, projects can set a variable named
CMAKE_GENERATOR_NO_COMPILER_ENV to a boolean true value and then CMake
will not set CC and CXX for any execute_process() call, even the initial
invocation.
add_custom_command()、add_custom_target()和命令execute_process()
为项目提供了很大的自由度。CMake 尚未直接支持的任何任务都可以使用主机操作系统提供的命令来实现。这些自定义命令本质上是特定于平台的,这违背了许多项目首先使用 CMake 的主要原因之一,即抽象出平台差异或至少以最小的努力支持一系列平台。
The add_custom_command(), add_custom_target() and execute_process()
commands provide projects with a great deal of freedom. Any task not already
directly supported by CMake can be implemented using commands provided by the
host operating system instead. These custom commands are inherently platform
specific, which works against one of the main reasons many projects use CMake
in the first place, i.e. to abstract away platform differences or to at least
support a range of platforms with minimal effort.
很大一部分自定义任务与文件系统操作相关。创建、删除、重命名或移动文件和目录构成了这些任务的大部分,但执行此操作的命令因操作系统而异。因此,项目通常最终会使用if-else条件来定义同一命令的不同平台版本,或者更糟糕的是,他们只需要为某些平台实现命令。许多开发人员不知道 CMake 提供了一种命令模式,该模式抽象了许多特定于平台的任务:
A large proportion of custom tasks are related to file system manipulation.
Creating, deleting, renaming or moving files and directories form the bulk of
these tasks, but the commands to do so vary between operating systems. As a
result, projects often end up using if-else conditions to define the
different platforms’ versions of the same command, or worse, they only bother
to implement the commands for some platforms. Many developers are not aware
that CMake provides a command mode which abstracts away many of these platform
specific tasks:
cmake -E cmd [参数...]
cmake -E cmd [args...]
可以使用 列出完整的受支持命令集cmake -E help,但一些更常用的命令包括:
The full set of supported commands can be listed using cmake -E help, but
some of the more commonly used ones include:
compare_files
compare_files
copy
copy
copy_directory
copy_directory
copy_if_different
copy_if_different
echo
echo
env
env
make_directory
make_directory
md5sum
md5sum
remove(CMake 3.17 已弃用)
remove (deprecated from CMake 3.17)
remove_directory(CMake 3.17 已弃用)
remove_directory (deprecated from CMake 3.17)
rename
rename
rm(需要 CMake 3.17 或更高版本)
rm (requires CMake 3.17 or later)
tar
tar
time
time
touch
touch
考虑删除特定目录及其所有内容的自定义任务的示例:
Consider the example of a custom task to remove a particular directory and all its contents:
set(discardDir "${CMAKE_CURRENT_BINARY_DIR}/private")
# Naive platform specific implementation (not robust)
if(WIN32)
add_custom_target(MyCleanup
COMMAND rmdir /S /Q "${discardDir}"
)
elseif(UNIX)
add_custom_target(MyCleanup
COMMAND rm -rf "${discardDir}"
)
else()
message(FATAL_ERROR "Unsupported platform")
endif()
# Platform independent equivalent
add_custom_target(MyCleanup
COMMAND "${CMAKE_COMMAND}" -E rm -R "${discardDir}"
)set(discardDir "${CMAKE_CURRENT_BINARY_DIR}/private")
# Naive platform specific implementation (not robust)
if(WIN32)
add_custom_target(MyCleanup
COMMAND rmdir /S /Q "${discardDir}"
)
elseif(UNIX)
add_custom_target(MyCleanup
COMMAND rm -rf "${discardDir}"
)
else()
message(FATAL_ERROR "Unsupported platform")
endif()
# Platform independent equivalent
add_custom_target(MyCleanup
COMMAND "${CMAKE_COMMAND}" -E rm -R "${discardDir}"
)
特定于平台的实现显示了项目通常如何尝试实现此场景,但 if-else 条件正在测试目标平台而不是主机平台。在交叉编译场景中,这可能会导致使用错误的命令。平台独立版本则没有这样的弱点。它始终为主机平台选择正确的命令。
The platform-specific implementation shows how projects often try to implement this scenario, but the if-else conditions are testing the target platform rather than the host platform. In a cross compiling scenario, this may result in the wrong command being used. The platform independent version has no such weakness. It always selects the right command for the host platform.
该示例还展示了如何cmake正确调用该命令。该
变量由 CMake 填充,它包含主构建中使用的可执行文件CMAKE_COMMAND的完整路径。cmake以这种方式使用
CMAKE_COMMAND可确保相同版本的 CMake 也用于自定义命令。可执行文件cmake不必是当前版本PATH,如果安装了多个版本的 CMake,则始终使用正确的版本,无论基于用户的PATH. 它还确保构建在构建阶段使用与配置阶段使用的相同的 CMake 版本,即使用户的PATH环境变量发生变化也是如此。
The example also shows how to invoke the cmake command correctly. The
CMAKE_COMMAND variable is populated by CMake and it contains the full
path to the cmake executable being used in the main build. Using
CMAKE_COMMAND in this way ensures that the same version of CMake is also used
for the custom command. The cmake executable does not have to be on the
current PATH and if multiple versions of CMake are installed, the correct
version is always used, regardless of which one might otherwise have been
selected based on the user’s PATH. It also ensures the build uses the same
CMake version during the build stage as was used in the configure stage, even
if the user’s PATH environment variable changes.
在本章前面,我们注意到和COMMENT的选项
并不总是可靠的。项目可以使用该命令将注释散布在自定义命令序列中的任何位置,而不是使用:add_custom_target()add_custom_command()COMMENT-E echo
Earlier in this chapter, it was noted that the COMMENT option for
add_custom_target() and add_custom_command() isn’t always reliable. Instead
of using COMMENT, projects can use the -E echo command to intersperse
comments anywhere in a sequence of custom commands:
set(discardDir "${CMAKE_CURRENT_BINARY_DIR}/private")
add_custom_target(MyCleanup
COMMAND ${CMAKE_COMMAND} -E
echo "Removing ${discardDir}"
COMMAND ${CMAKE_COMMAND} -E
rm -R "${discardDir}"
COMMAND ${CMAKE_COMMAND} -E
echo "Recreating ${discardDir}"
COMMAND ${CMAKE_COMMAND} -E
make_directory "${discardDir}"
)set(discardDir "${CMAKE_CURRENT_BINARY_DIR}/private")
add_custom_target(MyCleanup
COMMAND ${CMAKE_COMMAND} -E
echo "Removing ${discardDir}"
COMMAND ${CMAKE_COMMAND} -E
rm -R "${discardDir}"
COMMAND ${CMAKE_COMMAND} -E
echo "Recreating ${discardDir}"
COMMAND ${CMAKE_COMMAND} -E
make_directory "${discardDir}"
)
CMake 的命令模式是以独立于平台的方式执行一系列常见任务的非常有用的方法。然而,有时需要更复杂的逻辑,并且此类自定义任务通常使用特定于平台的 shell 脚本来实现。另一种方法是使用 CMake 本身作为脚本引擎,提供一种独立于平台的语言来表达任意逻辑。该命令-P的选项cmake将 CMake 置于脚本处理模式:
CMake’s command mode is a very useful way of carrying out a range of common
tasks in a platform independent way. Sometimes, however, more complex logic is
required and such custom tasks are often implemented using platform specific
shell scripts. An alternative is to use CMake itself as a scripting engine,
providing a platform independent language in which to express arbitrary logic.
The -P option to the cmake command puts CMake into script processing mode:
cmake [选项] -P 文件名
cmake [options] -P filename
参数filename是要执行的 CMake 脚本文件的名称。支持常用CMakeLists.txt语法,但没有配置或生成步骤,并且CMakeCache.txt文件未更新。脚本文件本质上只是作为一组命令而不是作为一个项目进行处理,因此不支持与构建目标或项目级功能相关的任何命令。尽管如此,脚本模式允许实现复杂的逻辑,并且具有不需要安装任何额外的 shell 解释器的优点。
The filename argument is the name of the CMake script file to execute. The
usual CMakeLists.txt syntax is supported, but there is no configure or
generate step and the CMakeCache.txt file is not updated. The script file is
essentially processed as just a set of commands rather than as a project, so
any commands which relate to build targets or project-level features are not
supported. Nonetheless, script mode allows complex logic to be implemented and
it comes with the advantage of not requiring any additional shell interpreter
to be installed.
虽然脚本模式不支持像普通 shell 或命令解释器那样的命令行选项,但它支持传递带有-D
选项的变量,就像普通cmake调用一样。由于CMakeCache.txt脚本模式下不会更新任何文件,因此-D可以自由使用选项,而不会影响主构建的缓存。此类选项必须放在 之前-P。
While script mode doesn’t support command line options like ordinary shells or
command interpreters, it does support passing in variables with -D
options, just like ordinary cmake invocations. Since no CMakeCache.txt file
is updated in script mode, -D options can be used freely without affecting
the main build’s cache. Such options must be placed before -P.
cmake -DOPTION_A=1 -DOPTION_B=foo -P myCustomScript.cmake
cmake -DOPTION_A=1 -DOPTION_B=foo -P myCustomScript.cmake
以下示例演示了本章讨论的许多功能:
The following example demonstrates many of the features discussed in this chapter:
cmake_minimum_required(VERSION 3.0)
project(Example)
# This executable generates files in a directory passed
# as a command line argument
add_executable(GenerateFiles generateFiles.cpp)
# Custom target to run the above executable and archive
# its results
set(outDir "foo")
add_custom_target(Archiver
COMMAND ${CMAKE_COMMAND} -E echo "Archiving files"
COMMAND ${CMAKE_COMMAND} -E rm -R "${outDir}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${outDir}"
COMMAND GenerateFiles "${outDir}"
COMMAND ${CMAKE_COMMAND} "-DTAR_DIR=${outDir}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/archiver.cmake"
)cmake_minimum_required(VERSION 3.0)
project(Example)
# This executable generates files in a directory passed
# as a command line argument
add_executable(GenerateFiles generateFiles.cpp)
# Custom target to run the above executable and archive
# its results
set(outDir "foo")
add_custom_target(Archiver
COMMAND ${CMAKE_COMMAND} -E echo "Archiving files"
COMMAND ${CMAKE_COMMAND} -E rm -R "${outDir}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${outDir}"
COMMAND GenerateFiles "${outDir}"
COMMAND ${CMAKE_COMMAND} "-DTAR_DIR=${outDir}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/archiver.cmake"
)
cmake_minimum_required(VERSION 3.0)
if(NOT TAR_DIR)
message(FATAL_ERROR "TAR_DIR must be set")
endif()
# Create an archive of the directory
set(archive archive.tar)
execute_process(
COMMAND ${CMAKE_COMMAND} -E
tar cf ${archive} "${TAR_DIR}"
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Archiving ${TAR_DIR} failed: ${result}"
)
endif()
# Compute MD5 checksum of the archive
execute_process(
COMMAND ${CMAKE_COMMAND} -E md5sum ${archive}
OUTPUT_VARIABLE md5output
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Unable to compute md5 of archive: ${result}"
)
endif()
# Extract just the checksum from the output
string(REGEX MATCH "^ *[^ ]*" md5sum "${md5output}")
message("Archive MD5 checksum: ${md5sum}")cmake_minimum_required(VERSION 3.0)
if(NOT TAR_DIR)
message(FATAL_ERROR "TAR_DIR must be set")
endif()
# Create an archive of the directory
set(archive archive.tar)
execute_process(
COMMAND ${CMAKE_COMMAND} -E
tar cf ${archive} "${TAR_DIR}"
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Archiving ${TAR_DIR} failed: ${result}"
)
endif()
# Compute MD5 checksum of the archive
execute_process(
COMMAND ${CMAKE_COMMAND} -E md5sum ${archive}
OUTPUT_VARIABLE md5output
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Unable to compute md5 of archive: ${result}"
)
endif()
# Extract just the checksum from the output
string(REGEX MATCH "^ *[^ ]*" md5sum "${md5output}")
message("Archive MD5 checksum: ${md5sum}")
上面显示了如何以不同的方式指定自定义任务。该示例的一个重要方面是它如何完成重要的事情,而无需诉诸平台特定的命令或功能。
The above shows how custom tasks can be specified in different ways. An important aspect of the example is how it accomplishes non-trivial things without having to resort to platform specific commands or functionality.
通过将归档逻辑放在单独的archiver.cmake文件中,它也可以在项目中使用,如上所示,也可以通过 CMake 的脚本模式单独调用。从开发和测试的角度来看,这可能很有用,或者提供一种独立于平台的方式来归档目录以供任何项目之外的一般使用。
By putting the archiving logic in the separate archiver.cmake file, it can
also be used either within a project as shown above, or it can be invoked on
its own through CMake’s script mode.
This can be useful from a development and testing perspective, or to provide a
platform-independent way of archiving a directory for general use outside of
any project.
当需要执行自定义任务时,最好在构建阶段而不是配置阶段完成它们。快速配置阶段很重要,因为当修改某些文件时(例如CMakeLists.txt项目中的任何文件、文件包含的任何文件
CMakeLists.txt或列为命令源的任何文件,configure_file()
如下一章所述),它可以自动调用。因此,
如果有选择的话,更愿意使用add_custom_target()oradd_custom_command()代替。execute_process()如果任务需要立即运行或作为cmake -P脚本模式调用的一部分,那么execute_process()是合适的。
When custom tasks need to be executed, it is preferable that they be done
during the build stage rather than the configure stage. A fast configure stage
is important because it can be invoked automatically when some files are
modified (e.g. any CMakeLists.txt file in the project, any file included by a
CMakeLists.txt file or any file listed as a source of a configure_file()
command as discussed in the next chapter). For this reason, prefer to use
add_custom_target() or add_custom_command() instead of execute_process()
if there is a choice.
If the task needs to run immediately or as part of a cmake -P script mode
invocation, then execute_process() is appropriate.
add_custom_command()与,add_custom_target()和一起使用的平台特定命令是相对常见的
execute_process()。然而,通常情况下,此类命令可以使用 CMake 的命令模式 ( ) 以独立于平台的方式表达-E。如果可能,应优先使用独立于平台的命令。此外,CMake 可以用作独立于平台的脚本语言,在使用选项调用时将文件作为 CMake 命令序列进行处理-P。使用 CMake 脚本而不是特定于平台的 shell 或单独安装的脚本引擎可以降低项目的复杂性并减少构建所需的额外依赖项。具体来说,考虑 CMake 的脚本模式是否比使用 Unix shell 脚本或 Windows 批处理文件更好,甚至是 Python、Perl 等语言的脚本(默认情况下可能并非在所有平台上都可用)。下一章将展示如何直接使用 CMake 操作文件,而不必求助于此类工具和方法。
It is relatively common to see platform specific commands used with
add_custom_command(), add_custom_target() and execute_process(). Quite
often, however, such commands can instead be expressed in a platform
independent manner using CMake’s command mode (-E). Where possible, the use
of platform independent commands should be preferred. In addition, CMake can
be used as a platform independent scripting language, processing a file as a
sequence of CMake commands when invoked with the -P option. The use of CMake
scripts instead of a platform specific shell or a separately installed script
engine can reduce the complexity of the project and reduce the additional
dependencies it requires in order to build. Specifically, consider whether
CMake’s script mode would be a better choice than using a Unix shell script or
Windows batch file, or even a script for a language like Python, Perl etc.
which may not be available by default on all platforms. The next chapter shows
how to manipulate files directly with CMake instead of having to resort to such
tools and methods.
在实现自定义任务时,尽量避免在所有情况下都缺乏支持的功能:
When implementing custom tasks, try to avoid features that lack support in all situations:
-E echo而不是COMMENT带有
add_custom_command()and的关键字add_custom_target()。
-E echo rather than the COMMENT keyword with
add_custom_command() and add_custom_target().
PRE_BUILD形式。TARGETadd_custom_command()
PRE_BUILD with the TARGET form of
add_custom_command().
IMPLICIT_DEPENDS或DEPFILEoptions with
是否add_custom_command()值得特定于生成器的行为。
IMPLICIT_DEPENDS or DEPFILE options with
add_custom_command() is worth the generator-specific behavior.
MAIN_DEPENDENCYin add_custom_command()
,除非目的是替换该源文件的默认构建规则。
MAIN_DEPENDENCY in add_custom_command()
unless the intention is to replace the default build rule for that source
file.
请特别注意自定义任务的输入和输出的依赖关系。确保创建的所有文件都add_custom_command()列为
OUTPUT文件。当将构建目标列为对add_custom_command()或 的调用中的命令或参数时add_custom_target(),更愿意将它们显式列为DEPENDS项目,而不是依赖 CMake 的自动目标依赖项处理。较弱的自动依赖关系可能无法强制执行开发人员直观期望的所有关系。DEPENDS如果在 或中列出文件
,请始终使用绝对路径以避免不可靠的旧路径匹配行为add_custom_target()。add_custom_command()
Pay special attention to dependencies for the inputs and outputs of custom
tasks. Ensure that all files created by add_custom_command() are listed as
OUTPUT files. When listing build targets as the command or arguments in a
call to add_custom_command() or add_custom_target(), prefer to explicitly
list them as DEPENDS items rather than relying on CMake’s automatic target
dependency handling. The weaker automatic dependencies may not enforce all the
relationships that developers may intuitively expect. If listing a file in
DEPENDS for either add_custom_target() or add_custom_command(), always
use an absolute path to avoid non-robust legacy path matching behavior.
调用时execute_process(),大多数时候应该通过使用命令捕获结果RESULT_VARIABLE并对其进行测试来测试命令是否成功if()。这包括TIMEOUT使用选项时,因为TIMEOUT它本身不会生成错误,它只会确保命令的运行时间不会超过指定的超时期限。
When calling execute_process(), most of the time the success of the command
should be tested by capturing the result using RESULT_VARIABLE and testing it
with the if() command. This includes when a TIMEOUT option is being used,
since TIMEOUT on its own will not generate an error, it will only ensure the
command doesn’t run longer than the nominated timeout period.
在某些类型的项目中,使用优化目标与非优化目标执行自定义命令之间的差异可能会对构建时间产生显着影响。一个常见的示例是使用自定义命令来运行本身作为项目一部分构建的代码生成器。如果代码生成是一个重要的过程,那么即使将项目的其余部分构建为“调试”,也可能需要使用“发布”配置来构建代码生成器。CMake 3.17 中引入的 Ninja Multi-Config 生成器是唯一直接支持此工作流程的生成器。鉴于该生成器的相当新的状态,在严重依赖此功能之前请仔细考虑。感兴趣的读者应查阅 CMake 文档,了解有关 Ninja 多重配置生成器的此功能和其他相关高级功能的更多详细信息。
In certain types of projects, the difference between executing custom commands with optimized targets versus non-optimized targets may have a noticeable effect on build times. A common example of this is where custom commands are used to run code generators that are themselves built as part of the project. If code generation is a non-trivial process, it may be desirable to have the code generators build with the Release configuration even when building the rest of the project as Debug. The Ninja Multi-Config generator introduced with CMake 3.17 is the only generator which directly supports this workflow. Given the fairly new status of this generator, consider carefully before relying heavily on this capability. The interested reader should consult the CMake documentation for further details on this and other related advanced features of the Ninja Multi-Config generator.
许多项目需要在构建过程中操作文件和目录。虽然此类操作的范围从琐碎到相当复杂,但更常见的任务包括:
Many projects need to manipulate files and directories as part of the build. While such manipulations range from trivial through to quite complex, the more common tasks include:
CMake 提供了与处理文件和目录相关的各种功能。在某些情况下,可以有多种方法来实现同一目标,因此了解不同的选择并了解如何有效地使用它们是很有用的。其中许多功能经常被滥用,其中一些是由于在线教程和示例中普遍存在这种滥用,导致人们相信这是正确的做事方式。本章讨论了一些更有问题的反模式。
CMake provides a variety of features related to working with files and directories. In some cases, there can be multiple ways of achieving the same thing, so it is useful to be aware of the different choices and understand how to use them effectively. A number of these features are frequently misused, some due to such misuse being prevalent in online tutorials and examples, leading to the belief that it is the right way to do things. Some of the more problematic anti-patterns are discussed in this chapter.
CMake 的许多与文件相关的功能都是由该file()命令提供的,还有一些其他命令提供了更适合某些情况的替代方案或提供相关的帮助程序功能。上一章中介绍的 CMake 的命令模式还提供了各种与文件相关的功能,这些功能与所file()提供的大部分功能重叠,但它涵盖了一组补充场景,而file()不是大多数情况下的替代方案。
Much of CMake’s file-related functionality is provided by the file() command,
with a few other commands offering alternatives better suited to certain
situations or providing related helper capabilities. CMake’s command mode,
which was introduced in the previous chapter, also provides a variety of
file-related features which overlap with much of what file() provides, but it
covers a complimentary set of scenarios to file() rather than being an
alternative in most cases.
文件处理最基本的部分之一是操作文件名和路径。项目中经常需要从完整路径中提取文件名、文件后缀等,或者在绝对路径和相对路径之间进行转换。在 CMake 3.19 及更早版本中,此功能分布在两个命令中,
get_filename_component()并且file(). 两者有一定的重叠,也有不一致的地方。CMake 3.20 引入了一个新命令,cmake_path()它取代了这两个命令的大部分路径处理功能。它提供了更一致、更可预测的界面。
One of the most basic parts of file handling is manipulating file names and
paths.
Projects often need to extract file names, file suffixes, etc. from full
paths, or convert between absolute and relative paths.
With CMake 3.19 and earlier, this functionality is spread across two commands,
get_filename_component() and file().
The two have some overlap and there are inconsistencies.
CMake 3.20 introduced a new command, cmake_path() which supersedes most
of the path-handling capabilities of those two commands.
It provides a more consistent, more predictable interface.
官方文档cmake_path()相当全面。它涵盖了所使用的概念,在基于任务的逻辑组中呈现了各种子命令,并提供了大量示例。我们鼓励读者研究其中提供的材料,以便更深入地理解该命令。此处仅描述一些用于更常见任务的关键概念和子命令。
The official documentation for cmake_path() is fairly comprehensive.
It covers the concepts used, presents the various sub-commands in logical,
task-based groups and provides numerous examples.
The reader is encouraged to study the material presented therein for a deeper
understanding of the command.
Only some key concepts and sub-commands for more common tasks are described
here.
该cmake_path()命令从不访问底层文件系统。它仅在语法上对路径进行操作,因此它对符号链接或路径的存在一无所知。它使用明确定义的路径结构,其中始终使用正斜杠作为目录分隔符,无论主机或目标平台如何。仅当主机平台支持时,才支持驱动器号或映射的驱动器名称(例如C:或)。//myserver
The cmake_path() command never accesses the underlying file system.
It only operates on paths syntactically, so it knows nothing about symbolic
links or the existence of a path.
It uses a clearly defined path structure where forward slashes are always used
as directory separators, regardless of the host or target platform.
A drive letter or mapped drive name (e.g. C: or //myserver) is supported
only if the host platform supports it.
以下示例路径和表演示了所使用的术语:
The following example path and table demonstrate the terminology used:
C:/一/二/start.middle.end
C:/one/two/start.middle.end
| 路径组件 | 例子 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
该GET子命令可用于检索上述任何路径组件(替换<COMP>为上表第一列中的一项):
The GET sub-command can be used to retrieve any of the above path components
(replace <COMP> with one of the items from the first column in the above
table):
cmake_path(GET pathVar <COMP> [LAST_ONLY] outVar)cmake_path(GET pathVar <COMP> [LAST_ONLY] outVar)
LAST_ONLY<COMP>仅当是EXTENSION或 时才能给出STEM。默认情况下, anEXTENSION从 的最左边的点 ( .) 字符
开始FILENAME,但LAST_ONLY将其更改为使用最右边的点字符。是STEM没有FILENAME的EXTENSION,关键字在这种情况下也LAST_ONLY
改变了 的含义。EXTENSION
LAST_ONLY can only be given if <COMP> is either EXTENSION or STEM.
By default, an EXTENSION starts at the left-most dot (.) character of the
FILENAME, but LAST_ONLY changes this to use the right-most dot character
instead.
The STEM is the FILENAME without the EXTENSION, with the LAST_ONLY
keyword changing the meaning of EXTENSION in this case as well.
set(path "a.b.c")
cmake_path(GET path EXTENSION result) # .b.c
cmake_path(GET path EXTENSION LAST_ONLY result) # .c
cmake_path(GET path STEM result) # a
cmake_path(GET path STEM LAST_ONLY result) # a.bset(path "a.b.c")
cmake_path(GET path EXTENSION result) # .b.c
cmake_path(GET path EXTENSION LAST_ONLY result) # .c
cmake_path(GET path STEM result) # a
cmake_path(GET path STEM LAST_ONLY result) # a.b
如上面的示例所示,请注意 是pathVar保存路径的变量的名称,它不是字符串:
As demonstrated in the above example, note that pathVar is the name of a
variable holding a path, it is not a string:
# WRONG: Cannot use a string for the path
cmake_path(GET "/some/path/example" FILENAME result)
# Correct, but can be improved (see below)
set(path "/some/path/example" FILENAME result)
cmake_path(GET path FILENAME result)# WRONG: Cannot use a string for the path
cmake_path(GET "/some/path/example" FILENAME result)
# Correct, but can be improved (see below)
set(path "/some/path/example" FILENAME result)
cmake_path(GET path FILENAME result)
虽然set()允许使用创建路径变量,但cmake_path()
有一个SET更强大且具有附加功能的专用子命令:
While using set() to create a path variable is permitted, cmake_path()
has a dedicated SET sub-command which is more robust and has additional
features:
cmake_path(SET pathVar [NORMALIZE] input)cmake_path(SET pathVar [NORMALIZE] input)
cmake_path(SET)使用over的主要优点set()是,前者会自动将本机路径转换为 cmake 样式路径,正如其他cmake_path()子命令所期望的那样。另一个优点是能够标准化路径。CMake 文档描述了该关键字含义的正式规则,但本质上它意味着通过解析诸如和 之NORMALIZE
类的内容来简化路径,将多个连续路径分隔符 ( ) 折叠为单个分隔符和一些其他特殊情况。请注意,由于从不访问文件系统,因此它不会将符号链接解析为规范化的一部分。.../cmake_path()
The main advantage of using cmake_path(SET) over set() is that the former
automatically converts a native path to a cmake-style path, as expected by
other cmake_path() sub-commands.
Another advantage is the ability to normalize the path.
The CMake documentation describes the formal rules for what the NORMALIZE
keyword implies, but essentially it means to simplify a path by resolving
things like . and .., collapsing multiple consecutive path separators
(/) down to a single separator and a few other special cases.
Note that because cmake_path() never accesses the file system, it does not
resolve symbolic links as part of normalization.
cmake_path(SET path NORMALIZE "/some//path/xxx/../example")
# The path variable now holds the value: /some/path/examplecmake_path(SET path NORMALIZE "/some//path/xxx/../example")
# The path variable now holds the value: /some/path/example
人们还可以查询路径以查看它是否具有特定的路径组件:
One can also query a path to see if it has a particular path component:
cmake_path(<OP> pathVar outVar)cmake_path(<OP> pathVar outVar)
的有效值<OP>包括与 的所有相同<COMP>值GET,但前缀HAS_(例如HAS_EXTENSION, HAS_RELATIVE_PART)除外。此外,IS_ABSOLUTE和IS_RELATIVE也是 的受支持值
<OP>,但它们有一些不太明显的行为方面需要注意。
IS_ABSOLUTE从技术上讲,路径明确地指代一个位置,而不需要指代某个相对点。其后果主要与 Windows 主机和路径相关,因为路径必须从根开始/,并且还具有被视为绝对的驱动器号。在非 Windows 主机平台上,具有驱动器号的路径被视为格式错误。因此,相同的路径在不同的主机平台上可能会产生不同的结果。如果在 Windows 主机上进行交叉编译并测试用于非 Windows 目标平台的路径,这尤其危险。
Valid values for <OP> include all the same <COMP> values for GET, except
prefixed with HAS_ (e.g. HAS_EXTENSION, HAS_RELATIVE_PART).
In addition, IS_ABSOLUTE and IS_RELATIVE are also supported values for
<OP>, but they have some less obvious behavioral aspects to be aware of.
IS_ABSOLUTE technically means that the path unambiguously refers to a
location without needing to refer to some relative point.
The consequences of this are mostly relevant for Windows hosts and paths,
since a path has to start at the root / and also have a drive letter to be
considered absolute.
On non-Windows host platforms, paths having a drive letter are considered
malformed.
The same path can therefore yield different results on different host
platforms.
This is especially dangerous if cross-compiling on a Windows host and testing
paths intended for a non-Windows target platform.
cmake_path(IS_ABSOLUTE pathVar result)
下表显示了不同情况下的结果:
The following table shows the result of cmake_path(IS_ABSOLUTE pathVar result)
for different cases:
| 路径变量 | Windows主机 | 其他主机平台 |
|---|---|---|
|
绝对 Absolute |
不明确的 Undefined |
|
相对的 Relative |
不明确的 Undefined |
|
相对的 Relative |
绝对 Absolute |
在所有平台上, 的结果IS_RELATIVE始终与 相反
IS_ABSOLUTE,因此它表现出相同的平台相关差异。
On all platforms, the result of IS_RELATIVE is always the opposite of
IS_ABSOLUTE, so it exhibits the same platform-dependent differences.
有一些子命令用于转换为绝对或相对路径:
There are sub-commands for converting to absolute or relative paths:
cmake_path(RELATIVE_PATH pathVar
[BASE_DIRECTORY baseDir]
[OUTPUT_VARIABLE outVar]
)
cmake_path(ABSOLUTE_PATH pathVar [NORMALIZE]
[BASE_DIRECTORY baseDir]
[OUTPUT_VARIABLE outVar]
)cmake_path(RELATIVE_PATH pathVar
[BASE_DIRECTORY baseDir]
[OUTPUT_VARIABLE outVar]
)
cmake_path(ABSOLUTE_PATH pathVar [NORMALIZE]
[BASE_DIRECTORY baseDir]
[OUTPUT_VARIABLE outVar]
)
相对路径被认为是相对于指定的baseDir(如果给定的话),否则CMAKE_CURRENT_SOURCE_DIR。如果没有outVar给出,则pathVar就地修改。该NORMALIZE关键字具有规范化结果路径的通常效果。还可以使用另一个子命令显式规范化路径:
Relative paths are considered relative to the specified baseDir, if given,
or CMAKE_CURRENT_SOURCE_DIR otherwise.
If no outVar is given, the pathVar is modified in-place.
The NORMALIZE keyword has the usual effect of normalizing the resultant
path.
One can also explicitly normalize a path with another sub-command:
cmake_path(NORMAL_PATH pathVar [OUTPUT_VARIABLE outVar])cmake_path(NORMAL_PATH pathVar [OUTPUT_VARIABLE outVar])
在 CMake 的所有文件处理中,大多数时候项目可以在所有平台上使用正斜杠作为目录分隔符,并且 CMake 会做正确的事情,根据项目的需要转换为本机路径。但是,有时项目可能需要在 CMake 和本机路径之间显式转换。一个这样的示例是在使用自定义命令并需要将路径传递给需要本机路径的脚本时。对于这些情况,NATIVE_PATH可以使用子命令:
Across all of CMake’s file handling, most of the time a project can use forward
slashes for directory separators on all platforms and CMake will do the right
thing, converting to native paths as necessary on the project’s behalf.
Occasionally, however, a project may need to explicitly convert between CMake
and native paths.
One such example is when working with custom commands and needing to pass
a path to a script which requires native paths.
For these situations, the NATIVE_PATH sub-command can be used:
cmake_path(NATIVE_PATH pathVar [NORMALIZE] outVar)cmake_path(NATIVE_PATH pathVar [NORMALIZE] outVar)
还有其他cmake_path()子命令,但上面介绍的子命令涵盖了最常见的用例。
There are other cmake_path() sub-commands, but the ones presented above
cover the most common use cases.
对于必须支持 CMake 3.19 或更早版本的项目,cmake_path()
无法使用该命令。尽管语法形式不太一致,但更旧的
get_filename_component()和file()命令或多或少可以使用相同的功能。这两个命令还可能作为解析路径的一部分访问文件系统,而cmake_path()后者则不然。
For projects that must support CMake 3.19 or earlier, the cmake_path()
command cannot be used.
The same capabilities are more or less available with the much older
get_filename_component() and file() commands though, albeit in
less consistent syntactic forms.
These two commands also potentially access the file system as part of resolving
paths, unlike cmake_path() which doesn’t.
在 CMake 3.19 及更早版本中,执行路径相关操作的主要方法是get_filename_component()命令。它具有三种不同的形式。第一种形式允许提取路径或文件名的不同部分,类似于以下提供的功能cmake_path(GET):
With CMake 3.19 and earlier, the primary method for performing path-related
operations is the get_filename_component() command.
It has three different forms.
The first form allows for the extraction of the different parts of a path or
file name, similar to the functionality provided by cmake_path(GET):
get_filename_component(outVar input component [CACHE])get_filename_component(outVar input component [CACHE])
调用的结果存储在名为 的变量中outVar。要从中提取的组件input由 指定component,它必须是以下之一:
The result of the call is stored in the variable named by outVar. The
component to extract from input is specified by component, which must be
one of the following:
DIRECTORY
DIRECTORY
input提取不带文件名的路径部分。在 CMake 2.8.12 之前,此选项曾经是PATH,它仍然被接受作为保持与旧版本兼容性的同义词DIRECTORY。
input without the file name. Prior to
CMake 2.8.12, this option used to be PATH, which is still accepted as a
synonym for DIRECTORY to preserve compatibility with older versions.
NAME
NAME
input.
input.
NAME_WE
NAME_WE
NAME除了文件名中直到但不包括第一个“.”的部分之外。被提取。
NAME except only
the part of the file name up to but not including the first "." is extracted.
NAME_WLE
NAME_WLE
NAME_WE与最后一个“.”之前的名称类似。被提取。此选项仅适用于 CMake 3.14 或更高版本。
NAME_WE except the name up to the last "." is
extracted. This option is only available with CMake 3.14 or later.
EXT
EXT
NAME_WE。它仅从第一个“.”中提取文件名的扩展名部分。向前。这可以被认为是文件名中最长的扩展名。
NAME_WE. It extracts just the extension
part of the file name from the first "." onward. This can be thought of as the
longest extension in the file name.
LAST_EXT
LAST_EXT
EXT只是返回最短的扩展名(即从最后一个“.”开始的文件名部分)。此选项仅适用于 CMake 3.14 或更高版本。
EXT except the shortest extension is returned
(i.e. the part of the file name from the last "." onward). This option is only
available with CMake 3.14 or later.
关键字CACHE是可选的。如果存在,结果将存储为缓存变量而不是常规变量。通常,不希望将结果存储在缓存中,因此CACHE通常不需要关键字。
The CACHE keyword is optional. If present, the result is stored as a cache
variable rather than a regular variable. Typically, it is not desirable to
store the result in the cache, so the CACHE keyword is not often required.
set(input /some/path/foo.bar.txt)
# /some/path
get_filename_component(path1 ${input} DIRECTORY)
get_filename_component(path2 ${input} PATH)
# foo.bar.txt
get_filename_component(fullName ${input} NAME)
# foo
get_filename_component(baseNameShort ${input} NAME_WE)
# foo.bar
get_filename_component(baseNameLong ${input} NAME_WLE)
# .bar.txt
get_filename_component(extensionLong ${input} EXT)
# .txt
get_filename_component(extensionShort ${input} LAST_EXT)set(input /some/path/foo.bar.txt)
# /some/path
get_filename_component(path1 ${input} DIRECTORY)
get_filename_component(path2 ${input} PATH)
# foo.bar.txt
get_filename_component(fullName ${input} NAME)
# foo
get_filename_component(baseNameShort ${input} NAME_WE)
# foo.bar
get_filename_component(baseNameLong ${input} NAME_WLE)
# .bar.txt
get_filename_component(extensionLong ${input} EXT)
# .txt
get_filename_component(extensionShort ${input} LAST_EXT)
的第二种形式get_filename_component()用于获取绝对路径:
The second form of get_filename_component() is used to obtain an absolute
path:
get_filename_component(outVar input component
[BASE_DIR baseDir] [CACHE]
)get_filename_component(outVar input component
[BASE_DIR baseDir] [CACHE]
)
在这种形式中,input可以是相对路径,也可以是绝对路径。如果
BASE_DIR给出,相对路径将被解释为相对于
baseDir而不是当前源目录(即
CMAKE_CURRENT_SOURCE_DIR)。
如果已经是绝对路径,BASE_DIR则将被忽略。input与 不同的是cmake_path(),此命令可以访问文件系统并解析符号链接。该component参数控制如何处理存储在中的路径的符号链接outVar:
In this form, input can be a relative path or it can be an absolute path. If
BASE_DIR is given, relative paths are interpreted as being relative to
baseDir instead of the current source directory (i.e.
CMAKE_CURRENT_SOURCE_DIR).
BASE_DIR will be ignored if input is already an absolute path.
Unlike cmake_path(), this command can access the file system and resolve
symbolic links.
The component argument controls how symbolic links are handled for the path
stored in outVar:
ABSOLUTE
ABSOLUTE
input而不解析符号链接。
input without resolving symbolic
links.
REALPATH
REALPATH
input计算已解析符号链接的绝对路径。
input with symbolic links resolved.
该file()命令提供逆操作,将绝对路径转换为相对路径:
The file() command provides the inverse operation, converting an absolute
path to relative:
file(RELATIVE_PATH outVar relativeToDir input)file(RELATIVE_PATH outVar relativeToDir input)
CMake 3.19还添加了一个file()子命令,其本质上相当于以下REALPATH操作get_filename_component():
CMake 3.19 also added a file() sub-command which is essentially equivalent to
the REALPATH operation of get_filename_component():
file(REAL_PATH input outVar
[BASE_DIRECTORY baseDir]
[EXPAND_TILDE] # Requires CMake 3.21 or later
)file(REAL_PATH input outVar
[BASE_DIRECTORY baseDir]
[EXPAND_TILDE] # Requires CMake 3.21 or later
)
在 CMake 3.21 或更高版本中,当EXPAND_TILDE给出关键字并input
以波形符 ( ~) 开头时,波形符将替换为用户主目录的路径。这模仿了大多数 Unix shell 的行为。
With CMake 3.21 or later, when the EXPAND_TILDE keyword is given and input
starts with a tilde (~), the tilde will be replaced by the path to the user’s
home directory.
This mimics the behavior of most Unix shells.
不幸的是,该file(REAL_PATH)子命令引入了许多不一致之处:
Unfortunately, the file(REAL_PATH) sub-command introduced a number of
inconsistencies:
input和参数的顺序outVar与补运算不同RELATIVE_PATH。
input and outVar arguments is different to the
complementary RELATIVE_PATH operation.
file()命令使用REAL_PATH(注意下划线),而
get_filename_component()使用REALPATH.
file() command uses REAL_PATH (note the underscore), whereas
get_filename_component() uses REALPATH.
BASE_DIRECTORYfor
file(REAL_PATH),而它被命名BASE_DIR为该
get_filename_component()命令。
BASE_DIRECTORY for
file(REAL_PATH), whereas it is named BASE_DIR for the
get_filename_component() command.
上述不一致可能会使这些命令的使用更容易出错,因此需要格外小心。
The above inconsistencies can make the use of these commands somewhat more error-prone, so extra care is needed.
file()以下示例演示了这些子命令的用法:
The following example demonstrates the usage of these file() sub-commands:
set(basePath /base)
set(fooBarPath /base/foo/bar)
set(otherPath /other/place)
file(RELATIVE_PATH fooBar ${basePath} ${fooBarPath})
file(RELATIVE_PATH other ${basePath} ${otherPath})
file(REAL_PATH ${other} otherReal
BASE_DIRECTORY ${basePath}
)set(basePath /base)
set(fooBarPath /base/foo/bar)
set(otherPath /other/place)
file(RELATIVE_PATH fooBar ${basePath} ${fooBarPath})
file(RELATIVE_PATH other ${basePath} ${otherPath})
file(REAL_PATH ${other} otherReal
BASE_DIRECTORY ${basePath}
)
在上述代码块的末尾,变量具有以下值:
At the end of the above code block, the variables have the following values:
fooBar = foo/酒吧 其他 = ../其他/地点 otherReal = /其他/地点
fooBar = foo/bar other = ../other/place otherReal = /other/place
该get_filename_component()命令的第三种形式可以方便地提取完整命令行的一部分(没有等效的cmake_path()
命令):
The third form of the get_filename_component() command is a convenience for
extracting parts of a full command line (there is no equivalent cmake_path()
command for this):
get_filename_component(progVar input PROGRAM
[PROGRAM_ARGS argVar] [CACHE]
)get_filename_component(progVar input PROGRAM
[PROGRAM_ARGS argVar] [CACHE]
)
使用这种形式,input假定是可能包含参数的命令行。CMake 将提取由指定命令行调用的可执行文件的完整路径,PATH如有必要,使用环境变量解析可执行文件的位置,并将结果存储在
progVar. 如果PROGRAM_ARGS给定,命令行参数集也会以列表的形式存储在名为 的变量中argVar。该CACHE关键字与其他形式的 具有相同的含义get_filename_component()。
With this form, input is assumed to be a command line which may contain
arguments. CMake will extract the full path to the executable which would be
invoked by the specified command line, resolving the executable’s location
using the PATH environment variable if necessary and store the result in
progVar. If PROGRAM_ARGS is given, the set of command line arguments are
also stored as a list in the variable named by argVar. The CACHE keyword
has the same meaning as the other forms of get_filename_component().
该file()命令提供了另外两种形式,有助于转换平台本机格式和 CMake 格式之间的路径:
The file() command offers two more forms which help transform paths between
platform native and CMake formats:
file(TO_NATIVE_PATH input outVar)
file(TO_CMAKE_PATH input outVar)file(TO_NATIVE_PATH input outVar)
file(TO_CMAKE_PATH input outVar)
该TO_NATIVE_PATH表单转换input为主机平台的本机路径。这相当于确保使用正确的目录分隔符(Windows 上的反斜杠,其他地方的正斜杠)。该TO_CMAKE_PATH表单将所有目录分隔符转换为input正斜杠。这是 CMake 在所有平台上使用的路径表示形式。输入还可以是以与平台PATH环境变量兼容的形式指定的路径列表。所有冒号分隔符都替换为分号,从而将PATH-like 输入转换为 CMake 路径列表。
The TO_NATIVE_PATH form converts input into a native path for the host
platform. This amounts to ensuring the correct directory separator is used
(backslash on Windows, forward slash everywhere else).
The TO_CMAKE_PATH form converts all directory separators in input to
forward slashes. This is the representation used by CMake for paths on all
platforms. The input can also be a list of paths specified in a form compatible
with the platform’s PATH environment variable. All colon separators are
replaced with semi-colons, thereby converting a PATH-like input into a CMake
list of paths.
# Unix example
set(customPath /usr/local/bin:/usr/bin:/bin)
file(TO_CMAKE_PATH ${customPath} outVar)
# outVar = /usr/local/bin;/usr/bin;/bin# Unix example
set(customPath /usr/local/bin:/usr/bin:/bin)
file(TO_CMAKE_PATH ${customPath} outVar)
# outVar = /usr/local/bin;/usr/bin;/bin
在配置阶段或构建过程中复制文件的需求相对常见。由于复制文件对于大多数用户来说通常是一项熟悉的任务,因此新 CMake 开发人员很自然地会使用他们已经知道的相同方法来实现文件复制。不幸的是,这通常会导致使用 和 来使用特定于平台的 shell 命令
add_custom_target(),add_custom_command()有时还会出现依赖性问题,需要开发人员多次运行 CMake 和/或以特定顺序手动构建目标。
The need to copy a file during the configure stage or during the build itself
is relatively common. Because copying a file is generally a familiar task
to most users, it is natural for new CMake developers to implement file copying
in terms of the same methods they already know. Unfortunately, this often
results in the use of platform-specific shell commands with
add_custom_target() and add_custom_command(), sometimes also with
dependency problems that require developers to run CMake multiple times and/or
manually build targets in a particular sequence.
在几乎所有情况下,CMake 都为此类特定于平台的方法提供了更好的替代方案。在本节中,介绍了许多复制文件的技术。有些旨在满足特定需求,而另一些则旨在更通用并可用于各种情况。所提供的所有方法在所有平台上的工作方式完全相同。
In almost all cases, CMake offers better alternatives to such platform-specific approaches. In this section, a number of techniques for copying files are presented. Some are aimed at meeting a particular need, while others are intended to be more generic and can be used in a variety of situations. All methods presented work exactly the same way on all platforms.
不幸的是,在配置时复制文件的最有用的命令之一是命名不太直观的命令之一。该configure_file()
命令允许将单个文件从一个位置复制到另一个位置,并可选择在此过程中执行 CMake 变量替换。复制是立即执行的,因此这是一个配置时操作。该命令的稍微简化的形式如下:
One of the most useful commands for copying files at configure time is,
unfortunately, one of the less intuitively named. The configure_file()
command allows a single file to be copied from one location to another,
optionally performing CMake variable substitution along the way. The copy is
performed immediately, so it is a configure-time operation. A slightly reduced
form of the command is as follows:
configure_file(source destination
[COPYONLY | @ONLY] [ESCAPE_QUOTES]
# See below for availability
[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
FILE_PERMISSIONS permissions...]
)configure_file(source destination
[COPYONLY | @ONLY] [ESCAPE_QUOTES]
# See below for availability
[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
FILE_PERMISSIONS permissions...]
)
必须source是现有文件,可以是绝对路径或相对路径,后者相对于当前源目录(即
CMAKE_CURRENT_SOURCE_DIR)。可以destination是现有目录或要复制到的文件名。使用目录名是有风险的,因为如果没有指定名称的目录,则会创建该名称的文件。可以destination包括绝对路径或相对路径。如果destination不是绝对路径,则将其解释为相对于当前二进制目录(即CMAKE_CURRENT_BINARY_DIR)。如果目标路径的任何部分不存在,CMake 将尝试创建缺失的目录作为调用的一部分。CMAKE_CURRENT_SOURCE_DIR请注意,项目包含或作为分别带有和 的CMAKE_CURRENT_BINARY_DIR路径的一部分并不罕见
,但这只会增加不必要的混乱,应该避免。sourcedestination
The source must be an existing file and can be an absolute or relative path,
with the latter being relative to the current source directory (i.e.
CMAKE_CURRENT_SOURCE_DIR).
The destination can be an existing directory or the file name to copy to.
Using a directory name is risky, since if there is no directory by the
specified name, a file of that name will be created instead.
The destination can include a path, which can be absolute or relative.
If the destination is not an absolute path, it is interpreted as being
relative to the current binary directory (i.e. CMAKE_CURRENT_BINARY_DIR).
If any part of the destination path does not exist, CMake will attempt to
create the missing directories as part of the call.
Note that it is not unusual to see projects include
CMAKE_CURRENT_SOURCE_DIR or CMAKE_CURRENT_BINARY_DIR as part of the path
with the source and destination respectively, but this just adds
unnecessary clutter and should be avoided.
默认情况下,目标文件将具有与源文件相同的权限。使用 CMake 3.19 或更高版本,NO_SOURCE_PERMISSIONS可以给出该选项,并且目标文件将可供所有人读取,仅可由用户写入且不可执行。用CMake 3.20以上版本,USE_SOURCE_PERMISSIONS还是FILE_PERMISSIONS可以使用的。前者已经是默认行为,但可以指定它以清楚地表明意图。
FILE_PERMISSIONS可以完全控制分配给目的地的权限。file(COPY)
和命令也支持这三个与权限相关的关键字file(GENERATE)。如何指定权限的示例包含在下面对这些命令的进一步讨论中。
By default, the destination file will have the same permissions as the source
file.
With CMake 3.19 or later, the NO_SOURCE_PERMISSIONS option can be given and
the destination file will be readable by everyone, writable by the user only
and not executable.
With CMake 3.20 or later, USE_SOURCE_PERMISSIONS or FILE_PERMISSIONS can
be used.
The former is already the default behavior, but it can be specified to clearly
indicate the intent.
FILE_PERMISSIONS gives full control over the permissions assigned to the
destination.
These three permission-related keywords are also supported by the file(COPY)
and file(GENERATE) commands.
Examples of how permissions can be specified are included in the discussion of
those commands further below.
如果source文件被修改,构建将认为该文件destination已过期并cmake自动重新运行。如果配置和生成时间很重要并且source文件被频繁修改,这可能会让开发人员感到沮丧。因此,configure_file()最好仅用于不需要经常更改的文件。
If the source file is modified, the build will consider the destination to
be out of date and will re-run cmake automatically. If the configure and
generation time is non-trivial and the source file is being modified
frequently, this can be a source of frustration for developers. For this
reason, configure_file() is best used only for files that don’t need to be
changed all that often.
执行复制时,configure_file()能够替换 CMake 变量。如果没有COPYONLY或@ONLY选项,
source文件中任何看起来像使用 CMake 变量(即具有形式
${someVar})的内容都将被该变量的值替换。如果不存在具有该名称的变量,则将替换为空字符串。形式的字符串
@someVar@也以相同的方式进行替换。下面展示了一些替换示例:
When performing the copy, configure_file() has the ability to substitute
CMake variables. Without the COPYONLY or @ONLY options, anything in the
source file that looks like a use of a CMake variable (i.e. has the form
${someVar}) will be replaced by the value of that variable. If no variable
exists with that name, an empty string is substituted. Strings of the form
@someVar@ are also substituted in the same way. The following shows a number
of substitution examples:
set(FOO "String with spaces")
configure_file(various.txt.in various.txt)set(FOO "String with spaces")
configure_file(various.txt.in various.txt)
CMake version: ${CMAKE_VERSION}
Substitution works inside quotes too: "${FOO}"
No substitution without the $ and {}: FOO
Empty ${} specifier gets removed
Escaping has no effect: \${FOO}
@-syntax also supported: @FOO@CMake version: ${CMAKE_VERSION}
Substitution works inside quotes too: "${FOO}"
No substitution without the $ and {}: FOO
Empty ${} specifier gets removed
Escaping has no effect: \${FOO}
@-syntax also supported: @FOO@
CMake version: 3.7.0
Substitution works inside quotes too: "String with spaces"
No substitution without the $ and {}: FOO
Empty specifier gets removed
Escaping has no effect: \String with spaces
@-syntax also supported: String with spacesCMake version: 3.7.0
Substitution works inside quotes too: "String with spaces"
No substitution without the $ and {}: FOO
Empty specifier gets removed
Escaping has no effect: \String with spaces
@-syntax also supported: String with spaces
该ESCAPE_QUOTES关键字可用于使任何替换的引号前面带有反斜杠。
The ESCAPE_QUOTES keyword can be used to cause any substituted quotes to be
preceded with a backslash.
set(BAR "Some \"quoted\" value")
configure_file(quoting.txt.in quoting.txt)
configure_file(quoting.txt.in quoting_escaped.txt
ESCAPE_QUOTES
)set(BAR "Some \"quoted\" value")
configure_file(quoting.txt.in quoting.txt)
configure_file(quoting.txt.in quoting_escaped.txt
ESCAPE_QUOTES
)
A: @BAR@
B: "@BAR@"A: @BAR@
B: "@BAR@"
A: Some "quoted" value
B: "Some "quoted" value"A: Some "quoted" value
B: "Some "quoted" value"
A: Some \"quoted\" value
B: "Some \"quoted\" value"A: Some \"quoted\" value
B: "Some \"quoted\" value"
如上面的示例所示,该ESCAPE_QUOTES选项会导致所有引号转义,无论其上下文如何。因此,当正在复制的文件对空格和可能执行的任何替换中的引用敏感时,必须小心谨慎。
As the above example shows, the ESCAPE_QUOTES option causes escaping of all
quotes regardless of their context. Therefore, a degree of care must be taken
when the file being copied is sensitive to spaces and quoting in any
substitutions which may be performed.
某些文件类型需要保留${someVar}形式而不进行替换。一个典型的例子是复制 Unix shell 脚本,这
${someVar}是引用 shell 变量的常见方法。在这种情况下,替换只能限于@someVar@带有@ONLY
关键字的形式:
Some file types need to have the ${someVar} form preserved without
substitution. A classic example of this is copying a Unix shell script where
${someVar} is a common way to refer to a shell variable. In such cases,
substitution can be limited to only the @someVar@ form with the @ONLY
keyword:
set(USER_FILE whoami.txt)
configure_file(whoami.sh.in whoami.sh @ONLY)set(USER_FILE whoami.txt)
configure_file(whoami.sh.in whoami.sh @ONLY)
#!/bin/sh
echo ${USER} > "@USER_FILE@"#!/bin/sh
echo ${USER} > "@USER_FILE@"
#!/bin/sh
echo ${USER} > "whoami.txt"#!/bin/sh
echo ${USER} > "whoami.txt"
也可以使用COPYONLY关键字完全禁用替换。如果知道不需要替换,则指定COPYONLY是一种很好的做法,因为它可以防止不必要的处理和任何意外的替换。
Substitution can also be disabled entirely with the COPYONLY keyword. If it
is known that substitution is not needed, specifying COPYONLY is good
practice, since it prevents unnecessary processing and any unexpected
substitutions.
使用configure_file()和替换文件名或路径时,一个常见的错误是错误处理空格和引号。如果需要将替换变量视为单个路径或文件名,则源文件可能需要用引号将其括起来。这就是为什么上例中使用源文件
"@USER_FILE@"而不是@USER_FILE@作为文件名来写入输出的原因。
When using configure_file() and substituting file names or paths, a common
mistake is to mishandle spaces and quoting. The source file may need to
surround a substituted variable with quotes if it needs to be treated as a
single path or file name. This is why the source file in the above example used
"@USER_FILE@" rather than @USER_FILE@ as the filename to write the output
to.
${someVar}或形式
的 CMake 变量替换@someVar@也可以在字符串上执行,而不仅仅是文件。该string(CONFIGURE)命令提供等效的功能和选项。当要复制的内容需要比简单替换更复杂的步骤时,它会很有用:
Substitution of CMake variables in ${someVar} or @someVar@ form can also be
performed on strings, not just files.
The string(CONFIGURE) command provides equivalent functionality and options.
It can be useful when the content to be copied requires more complex steps than
a simple substitution:
string(CONFIGURE input outVar [@ONLY] [ESCAPE_QUOTES])string(CONFIGURE input outVar [@ONLY] [ESCAPE_QUOTES])
该configure_file()命令使用文件进行输入和输出。子string(CONFIGURE)命令使用字符串进行输入和输出。CMake 3.18 添加了第三种方法,支持使用字符串作为输入和文件作为输出:
The configure_file() command uses files for input and output.
The string(CONFIGURE) sub-command uses strings for input and output.
CMake 3.18 added a third method that supports using a string for input and a
file for output:
file(CONFIGURE
OUTPUT outFile
CONTENT inputString
[@ONLY] [ESCAPE_QUOTES]
# ... Other rarely used options
)file(CONFIGURE
OUTPUT outFile
CONTENT inputString
[@ONLY] [ESCAPE_QUOTES]
# ... Other rarely used options
)
和分别OUTPUT指定CONTENT输出文件和输入字符串。如果outFile是相对路径,则假定它是相对于当前二进制目录的。其余选项的含义与命令的含义完全相同
configure_file()。
The OUTPUT and CONTENT specify the output file and input string
respectively.
If the outFile is a relative path, it is assumed to be relative to the
current binary directory.
The remaining options have exactly the same meaning as they do for the
configure_file() command.
如果不需要替换,另一种选择是使用相关file()子命令之一。最灵活和最成熟的,可用于所有 CMake 版本,是密切相关的COPY和INSTALL形式,它们都支持相同的选项集:
Where no substitution is needed, another alternative is to use one of the
relevant file() sub-commands.
The most flexible and mature, available with all CMake versions, are the
closely related COPY and INSTALL forms, both of which support the same set
of options:
file(<COPY|INSTALL> fileOrDir1 [fileOrDir2...]
DESTINATION dir
[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]]
[FOLLOW_SYMLINK_CHAIN] # Requires CMake 3.15 or later
[FILES_MATCHING]
[ [PATTERN pattern | REGEX regex] [EXCLUDE]
[PERMISSIONS permissions...] ] [...]
)file(<COPY|INSTALL> fileOrDir1 [fileOrDir2...]
DESTINATION dir
[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]]
[FOLLOW_SYMLINK_CHAIN] # Requires CMake 3.15 or later
[FILES_MATCHING]
[ [PATTERN pattern | REGEX regex] [EXCLUDE]
[PERMISSIONS permissions...] ] [...]
)
可以将多个文件甚至整个目录层次结构复制到所选目录,甚至保留符号链接(如果存在)。任何未指定绝对路径的源文件或目录都被视为相对于当前源目录。同样,如果目标目录不是绝对的,它将被解释为相对于当前二进制目录。根据需要创建目标目录结构。
Multiple files or even entire directory hierarchies can be copied to a chosen directory, even preserving symlinks if present. Any source files or directories specified without an absolute path are treated as being relative to the current source directory. Similarly, if the destination directory is not absolute, it will be interpreted as being relative to the current binary directory. The destination directory structure is created as necessary.
如果源是目录名称,它将被复制到目标中。要将目录的内容复制到目标中,请将正斜杠 (/) 附加到源目录,如下所示:
If a source is a directory name, it will be copied into the destination. To copy the directory’s contents into the destination instead, append a forward slash (/) to the source directory like so:
# --> destDir/srcDir
file(COPY base/srcDir DESTINATION destDir)
# --> destDir
file(COPY base/srcDir/ DESTINATION destDir)# --> destDir/srcDir
file(COPY base/srcDir DESTINATION destDir)
# --> destDir
file(COPY base/srcDir/ DESTINATION destDir)
默认情况下,该COPY表单将导致所有文件和目录保留与复制源相同的权限,而该
INSTALL表单不会保留原始权限。和
选项可用于覆盖这些默认值,或者可以使用和选项显NO_SOURCE_PERMISSIONS式指定权限。权限值基于 Unix 系统支持的值:USE_SOURCE_PERMISSIONSFILE_PERMISSIONSDIRECTORY_PERMISSIONS
By default, the COPY form will result in all files and directories keeping
the same permissions as the source from which they are copied, whereas the
INSTALL form will not preserve the original permissions. The
NO_SOURCE_PERMISSIONS and USE_SOURCE_PERMISSIONS options can be used to
override these defaults, or the permissions can be explicitly specified with
the FILE_PERMISSIONS and DIRECTORY_PERMISSIONS options. The permission
values are based on those supported by Unix systems:
|
|
|
|
|
|
|
|
|
|
|
如果特定的权限在给定平台上不被理解,它就会被忽略。多个权限可以(并且通常)一起列出。例如,可以将 Unix shell 脚本复制到当前二进制目录,如下所示:
If a particular permission is not understood on a given platform, it is simply ignored. Multiple permissions can be (and usually are) listed together. For example, a Unix shell script might be copied to the current binary directory as follows:
file(COPY whoami.sh
DESTINATION .
FILE_PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)file(COPY whoami.sh
DESTINATION .
FILE_PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
和签名还保留被复制COPY的INSTALL文件和目录的时间戳。此外,如果源已经存在于具有相同时间戳的目的地,则该文件的副本被视为已经完成并将被跳过。COPY除了默认权限之外,唯一的其他区别INSTALL是INSTALL表单打印每个复制项目的状态消息,而
COPY不会打印。这种差异是因为该INSTALL表单通常用作在脚本模式下运行的 CMake 脚本的一部分来安装文件,其中常见的行为是打印每个已安装文件的名称。
The COPY and INSTALL signatures both also preserve the timestamps of the
files and directories being copied. Furthermore, if the source is already
present at the destination with the same timestamp, the copy for that file is
deemed as already having been done and will be skipped. The only other
difference between COPY and INSTALL apart from the default permissions is
that the INSTALL form prints status messages for each copied item, whereas
COPY does not. This difference is because the INSTALL form is typically
used as part of CMake scripts run in script mode for installing files, where
common behavior is to print the name of each file installed.
CMake 3.15 或更高版本FOLLOW_SYMLINK_CHAIN支持该关键字。当存在此选项时,将递归复制要复制/安装的文件列表中的符号链接,并保留符号链接。递归停止,最终的非符号链接文件正常复制。当像这样复制或安装符号链接时,所有路径都会被删除,因此此功能实际上只适合符号链接指向同一目录中的内容的情况。
With CMake 3.15 or later, the FOLLOW_SYMLINK_CHAIN keyword is supported.
When this option is present, symlinks in the list of files to copy/install will
be copied recursively, with symlinks being preserved.
The recursion stops with the final non-symlink file being copied as normal.
When copying or installing symlinks like this, all paths are stripped off, so
this functionality really only suits situations where symlinks point to things
in the same directory.
考虑 Linux 上一组相当标准的库符号链接,如下所示:
Consider a fairly standard set of library symlinks on Linux, such as the following:
libMyStuff.so.2.4.3 libMyStuff.so.2 --> libMyStuff.so.2.4.3 libMyStuff.so --> libMyStuff.so.2
libMyStuff.so.2.4.3 libMyStuff.so.2 --> libMyStuff.so.2.4.3 libMyStuff.so --> libMyStuff.so.2
如果libMyStuff.so给file(COPY)orfile(INSTALL)命令并且FOLLOW_SYMLINK_CHAIN存在该选项,则以上所有三个都将被复制/安装,并且相对符号链接将全部保留,如图所示。请注意,符号链接仅在一个方向上遵循,没有用于查找链接到列出的文件的内容的逻辑。因此,对于上面的示例,如果仅libMyStuff.so.2在命令中列出file()
,则libMyStuff.so不会发现符号链接,因此不会复制/安装它。
If libMyStuff.so was given to the file(COPY) or file(INSTALL) command and
the FOLLOW_SYMLINK_CHAIN option was present, all three of the above would be
copied/installed and the relative symlinks would all be preserved exactly as
shown.
Note that the symlinks are only followed in one direction, there is no logic
for finding things that link to a file listed.
So for the above example, if only libMyStuff.so.2 was listed in the file()
command, the libMyStuff.so symlink would not be discovered and therefore it
wouldn’t be copied/installed.
和COPY都INSTALL支持将某些逻辑应用于匹配或不匹配特定通配符模式或正则表达式的文件。这可用于限制复制哪些文件并覆盖仅匹配文件的权限。一条命令中可以给出多种模式和正则表达式file()。通过示例可以最好地演示其用法。
Both COPY and INSTALL support applying certain logic to files that
match or do not match a particular wildcard pattern or regular expression. This
can be used to limit which files are copied and to override the permissions
just for the matched files. Multiple patterns and regular expressions can be
given in the one file() command. The use is best demonstrated by example.
以下代码复制所有 header( .h) 和 script( .sh) 文件someDir,但文件名以 . 结尾的标头除外_private.h。标头被赋予与复制它们的文件相同的权限,而脚本被赋予所有者读取、写入和执行权限。目录结构被保留。
The following copies all header (.h) and script (.sh) files from someDir,
except headers whose file name ends with _private.h.
Headers are given the same permissions as the file they are copied from,
whereas scripts are given owner read, write and execute permissions.
The directory structure is preserved.
file(COPY someDir
DESTINATION .
FILES_MATCHING
REGEX .*_private\\.h EXCLUDE
PATTERN *.h
PATTERN *.sh PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
)file(COPY someDir
DESTINATION .
FILES_MATCHING
REGEX .*_private\\.h EXCLUDE
PATTERN *.h
PATTERN *.sh PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
)
如果应复制整个源,但仅需要覆盖匹配文件的子集的权限,则FILES_MATCHING可以省略关键字,并且模式和正则表达式纯粹用于应用权限覆盖。
If the whole source should be copied but permissions need to be overridden for
just a subset of matched files, the FILES_MATCHING keyword can be omitted
and the patterns and regular expressions are used purely to apply permission
overrides.
file(COPY someDir
DESTINATION .
# Make Unix shell scripts executable by everyone
PATTERN *.sh PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
# Ensure only owner can read/write private key files
REGEX _dsa\$|_rsa\$ PERMISSIONS
OWNER_READ OWNER_WRITE
)file(COPY someDir
DESTINATION .
# Make Unix shell scripts executable by everyone
PATTERN *.sh PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
# Ensure only owner can read/write private key files
REGEX _dsa\$|_rsa\$ PERMISSIONS
OWNER_READ OWNER_WRITE
)
对于非常简单的文件复制操作,CMake 3.21 或更高版本提供了一个替代子命令,一些开发人员可能会发现它更易于使用:
For very simple file copying operations, CMake 3.21 or later provides an alternative sub-command that some developers may find easier to use:
file(COPY_FILE source destination
[RESULT result]
[ONLY_IF_DIFFERENT]
)file(COPY_FILE source destination
[RESULT result]
[ONLY_IF_DIFFERENT]
)
如果给出,则如果文件已经具有与文件相同的内容,则ONLY_IF_DIFFERENT时间戳将不会被更新。通常建议包含此选项,以避免触发任何依赖于.destinationsourcedestination
If ONLY_IF_DIFFERENT is given, then the timestamp of the destination will
not be updated if it already has the same contents as the source file.
It will typically be advisable to include this option to avoid triggering
unnecessary rebuilds of anything that depends on the destination.
如果RESULT给出关键字,命令的结果将存储在指定的变量中。这允许处理继续并从错误中恢复。成功时存储的值outVar将为 0,否则会出现错误消息。如果没有RESULT关键字,如果遇到任何错误,处理将停止。
If the RESULT keyword is given, the outcome of the command is stored in the
named variable.
This allows processing to continue and recover from an error.
The value stored in outVar will be 0 upon success or an error message
otherwise.
Without the RESULT keyword, processing will halt if any error is encountered.
CMake 提供了更多复制文件和目录的方法。和主要在配置时或安装时在 CMake 脚本中使用,configure_file()而file()CMake 的命令模式通常用于在构建时复制文件和目录。add_custom_target()命令模式是复制内容作为规则
一部分的首选方式add_custom_command(),因为它提供平台独立性(请参见
第 18.5 节“平台独立命令”)。与复制相关的命令有三个,第一个用于复制单个文件:
CMake offers further methods for copying files and directories.
Whereas configure_file() and file() are used mainly at configure time or
in a CMake script at install time, CMake’s command mode is often
used for copying files and directories at build time. Command mode is the
preferred way to copy content as part of add_custom_target() and
add_custom_command() rules, since it provides platform independence (see
Section 18.5, “Platform Independent Commands”). There are three commands related to
copying, the first of which is used to copy individual files:
cmake -E 复制 file1 [file2...] 目的地
cmake -E copy file1 [file2...] destination
如果仅提供一个源文件,则 then destination被视为要复制到的文件的名称,除非它指定了现有目录。当目标是现有目录时,源文件将被复制到其中。此行为与大多数操作系统的本机复制命令一致,但这也意味着该行为取决于复制操作之前文件系统的状态。因此,在复制单个文件时显式指定目标文件名会更加稳健,除非保证目标是已经存在的目录。
If only one source file is provided, then destination is treated as the
name of the file to copy to, unless it names an existing directory. When the
destination is an existing directory, the source file will be copied into it.
This behavior is consistent with that of most operating systems’ native copy
commands, but it also means that the behavior is dependent on the state of the
file system before the copy operation. Therefore, it is more robust to
explicitly specify the target file name when copying a single file
unless it is guaranteed that the destination is a directory that will already
exist.
为方便起见,如果destination包含路径(相对或绝对),CMake 将在仅复制单个源文件时尝试根据需要创建目标路径。这意味着在复制单个文件时,该copy
命令不需要之前的步骤来确保目标目录存在。如果列出多个源文件,则destination必须引用现有目录。再次,CMake 的命令模式可用于确保这一点,make_directory如果命名目录尚不存在,则创建该目录,包括根据需要的任何父目录。下面展示了如何安全地将这些命令模式命令组合在一起:
As a convenience, if destination includes a path (relative or absolute),
CMake will try to create the destination path as needed when copying only a
single source file. This means that when copying individual files, the copy
command does not require an earlier step to ensure the destination directory
exists. If more than one source file is listed, destination must
refer to an existing directory. Once again, CMake’s command mode can be used to
ensure this using make_directory which creates the named directory if it does
not already exist, including any parent directories as needed. The following
shows how to safely put these command mode commands together:
add_custom_target(CopyOne
COMMAND ${CMAKE_COMMAND} -E
copy a.txt output/textfiles/a.txt
)
add_custom_target(CopyTwo
COMMAND ${CMAKE_COMMAND} -E
make_directory output/textfiles
COMMAND ${CMAKE_COMMAND} -E
copy a.txt b.txt output/textfiles
)add_custom_target(CopyOne
COMMAND ${CMAKE_COMMAND} -E
copy a.txt output/textfiles/a.txt
)
add_custom_target(CopyTwo
COMMAND ${CMAKE_COMMAND} -E
make_directory output/textfiles
COMMAND ${CMAKE_COMMAND} -E
copy a.txt b.txt output/textfiles
)
该copy命令始终会将源复制到目标,即使目标已经与源相同。这会导致目标时间戳始终被更新,这有时是不可取的。如果文件已经匹配则不应更新时间戳,则
copy_if_different命令可能更合适:
The copy command will always copy the source to the destination, even if the
destination is already identical to the source. This results in the target
timestamps always being updated, which can sometimes be undesirable. If the
timestamps should not be updated if the files already match, then the
copy_if_different command may be more appropriate:
cmake -E copy_if_ different file1 [file2...] 目标
cmake -E copy_if_different file1 [file2...] destination
此功能与该命令完全相同copy,除非源文件已存在于目标并且与源相同,则不执行复制并且目标的时间戳保持不变。也可以复制整个目录而不是单个文件:
This functions exactly like the copy command except if a source file already
exists at the destination and is the same as the source, no copy is performed
and the timestamp of the target is left alone.
It is also possible to copy entire directories rather than individual files:
cmake -E copy_directory dir1 [dir2...] 目的地
cmake -E copy_directory dir1 [dir2...] destination
与文件相关的复制命令不同,如果需要,将创建目标目录,包括任何中间路径。另请注意,将源目录的内容copy_directory
复制到目标,而不是源目录本身。例如,假设目录
包含一个文件并发出以下命令:myDirsomeFile.txt
Unlike the file-related copy commands, the destination directory is created if
required, including any intermediate path. Note also that copy_directory
copies the contents of the source directories into the destination, not the
source directories themselves. For example, suppose a directory myDir
contains a file someFile.txt and the following command was issued:
cmake -E copy_directory myDir targetDir
cmake -E copy_directory myDir targetDir
结果将targetDir包含该文件someFile.txt,而不是myDir/someFile.txt.
The result would be that targetDir would contain the file someFile.txt,
not myDir/someFile.txt.
一般来说,configure_file()和file()最适合在配置时复制文件,而 CMake 的命令模式是在构建时复制的首选方式。虽然可以execute_process()在配置时结合使用命令模式来复制文件,但没有理由这样做,因为configure_file()和file()都更直接,并且具有在出现任何错误时自动停止的额外好处。
Generally speaking, configure_file() and file() are best suited to copying
files at configure time, whereas CMake’s command mode is the preferred way to
copy at build time. While it is possible to use command mode in conjunction
with execute_process() to copy files at configure time, there is little
reason to do so, since configure_file() and file() are both more direct and
have the added benefit that they stop on any error automatically.
CMake 不仅提供复制文件的功能,还提供许多用于读取和写入文件内容的命令。该file()
命令提供了大部分功能,最简单的是直接写入文件的形式:
CMake offers more than just the ability to copy files, it also provides a
number of commands for reading and writing file contents. The file()
command provides the bulk of the functionality, with the simplest being the
forms that write directly to a file:
file(WRITE fileName content)
file(APPEND fileName content)file(WRITE fileName content)
file(APPEND fileName content)
这两个命令都会将指定的内容写入content指定的文件中。两者唯一的区别是,如果fileName已经存在,
APPEND则会追加到现有内容上,而WRITE在写入之前会丢弃现有内容。就像content任何其他函数参数一样,可以是变量或字符串的内容。
Both of these commands will write the specified content to the named file.
The only difference between the two is that if fileName already exists,
APPEND will append to the existing contents, whereas WRITE will discard the
existing contents before writing. The content is just like any other function
argument and can be the contents of a variable or a string.
set(msg "Hello world")
file(WRITE hello.txt ${msg})
file(APPEND hello.txt " from CMake")set(msg "Hello world")
file(WRITE hello.txt ${msg})
file(APPEND hello.txt " from CMake")
hello.txt上面的结果将生成包含单行的
文件Hello world from CMake。请注意,换行符不会自动添加,因此该APPEND
行中的文本直接在该WRITE行文本之后继续,没有中断。要写入换行符,它必须包含在传递给
file()命令的内容中。一种方法是使用跨多行的引用值:
The above would result in the file hello.txt containing the single line
Hello world from CMake.
Note that newlines are not automatically added, so the text from the APPEND
line continues directly after the WRITE line’s text without a break.
To have a newline written, it must be included in the content passed to the
file() command.
One way is to use a quoted value spread across multiple lines:
file(WRITE multi.txt "First line
Second line
")file(WRITE multi.txt "First line
Second line
")
如果使用 CMake 3.0 或更高版本,第 5.1 节“变量基础知识” 中引入的括号语法 有时会更方便,因为它可以防止内容的任何变量替换。
If using CMake 3.0 or later, the bracket syntax introduced back in Section 5.1, “Variable Basics” can sometimes be more convenient, since it prevents any variable substitution of the content.
file(WRITE multi.txt [[
First line
Second line
]])
file(WRITE userCheck.sh [=[
#!/bin/bash
[[ -n "${USER}" ]] && echo "Have USER"
]=])file(WRITE multi.txt [[
First line
Second line
]])
file(WRITE userCheck.sh [=[
#!/bin/bash
[[ -n "${USER}" ]] && echo "Have USER"
]=])
上面要写入的内容multi.txt只是简单的文本,没有特殊字符,所以最简单的=
可以省略字符的括号语法就足够了,只留下一对方括号来标记内容的开始和结束。请注意在左括号后立即忽略第一个换行符的行为如何使命令更具可读性。
In the above, the contents to be written to multi.txt consist only of simple
text with no special characters, so the simplest bracket syntax where =
characters can be omitted is sufficient, leaving just a pair of square brackets
to mark the start and end of the content. Note how the behavior to ignore the
first newline immediately after the opening bracket makes the command more
readable.
的内容userCheck.sh更加有趣并且突出了括号语法的特点。如果没有括号语法,CMake 会看到该
${USER}部分并将其视为 CMake 变量替换,但由于括号语法不执行此类替换,因此它会保持原样。出于同样的原因,内容中的各种引号字符也不会被解释为内容的一部分以外的任何内容。不需要对它们进行转义以防止它们被解释为参数的开始或结束。此外,请注意嵌入内容如何包含一对方括号。这就是开始和结束标记中可变数量的符号旨在处理的情况=,允许选择标记,以便它们与它们周围的内容中的任何内容都不匹配。将多行写入文件并且不应执行替换时,括号语法通常是指定要写入的内容的最方便的方法。
The contents for userCheck.sh are much more interesting and highlight the
features of bracket syntax. Without bracket syntax, CMake would see the
${USER} part and treat it as a CMake variable substitution, but because
bracket syntax performs no such substitution, it is left as is. For the same
reason, the various quote characters in the content are also not interpreted as
anything other than part of the content. They do not need to be escaped to
prevent them being interpreted as the start or end of an argument. Furthermore,
note how the embedded contents contain a pair of square brackets. This is the
sort of situation the variable number of = signs in the start and end markers
is meant to handle, allowing the markers to be chosen so that they do not match
anything in the content they surround. When writing out multiple lines to a
file and when no substitution should be performed, bracket syntax is often the
most convenient way to specify the content to be written.
有时,项目可能需要编写一个文件,其内容取决于构建类型。一种幼稚的方法是假设CMAKE_BUILD_TYPE变量可以用作替换,但这不适用于 Xcode、Visual Studio 或 Ninja Multi-Config 等多配置生成器。相反,file(GENERATE…)可以使用该命令:
Sometimes a project may need to write a file whose contents depend on the build
type. A naive approach would be to assume the CMAKE_BUILD_TYPE variable
could be used as a substitution, but this does not work for multi configuration
generators like Xcode, Visual Studio or Ninja Multi-Config.
Instead, the file(GENERATE…) command can be used:
file(GENERATE
OUTPUT outFile
INPUT inFile | CONTENT content
[CONDITION expression]
# Requires CMake 3.19 or later:
[TARGET target]
# Requires CMake 3.20 or later:
[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
[FILE_PERMISSIONS permissions...]
)file(GENERATE
OUTPUT outFile
INPUT inFile | CONTENT content
[CONDITION expression]
# Requires CMake 3.19 or later:
[TARGET target]
# Requires CMake 3.20 or later:
[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
[FILE_PERMISSIONS permissions...]
)
这有点类似,file(WRITE…)只是它为当前 CMake 生成器支持的每种构建类型写出一个文件。INPUT或选项中的一个CONTENT必须存在,但不能同时存在。它们定义要写入指定输出文件的内容。
This works somewhat like file(WRITE…) except that it writes out one file
for each build type supported for the current CMake generator.
Either of the INPUT or CONTENT options must be present, but not both.
They define the content to be written to the specified output file.
outFile,inFile并且content都支持生成器表达式,这就是为每种构建类型自定义文件名和内容的方式。使用该CONDITION选项可以跳过构建类型。expression在扩展任何生成器表达式后,必须求值为 0 或 1 。如果计算结果为 0,则不会生成输出文件。
outFile, inFile and content all support generator expressions, which is
how the file names and contents are customized for each build type.
A build type can be skipped by using the CONDITION option.
The expression must evaluate to either 0 or 1 after any generator
expressions have been expanded.
The output file is not generated if it evaluates to 0.
如果任何参数中的生成器表达式需要一个目标来对其进行求值,但目标不是表达式的一部分(例如$<TARGET_PROPERTY:propName>),则TARGET必须提供该选项以便可以解析它。TARGET仅 CMake 3.19 或更高版本支持该选项。
If a generator expression in any of the arguments needs a target for it to be
evaluated, but the target is not part of the expression
(e.g. $<TARGET_PROPERTY:propName>), the TARGET option must be provided so
it can be resolved.
The TARGET option is only supported with CMake 3.19 or later.
使用 CMake 3.20 或更高版本,可以按照与或outFile相同的方式指定的权限。and关键字仅在使用选项指定输入文件
时才有效,但可以与或一起使用。configure_file()file(COPY)NO_SOURCE_PERMISSIONSUSE_SOURCE_PERMISSIONSINPUTFILE_PERMISSIONSINPUTCONTENT
With CMake 3.20 or later, the permissions for the outFile can be specified
in the same way as for configure_file() or file(COPY).
The NO_SOURCE_PERMISSIONS and USE_SOURCE_PERMISSIONS keywords are only
valid if using the INPUT option to specify an input file, but
FILE_PERMISSIONS can be used with either INPUT or CONTENT.
以下示例展示了如何使用生成器表达式根据构建类型自定义内容和文件名。
The following examples show how to make use of generator expressions to customize the contents and file names depending on the build type.
# Generate unique files for all but Release
file(GENERATE
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/outFile-$<CONFIG>.txt
INPUT
${CMAKE_CURRENT_SOURCE_DIR}/input.txt.in
CONDITION
$<NOT:$<CONFIG:Release>>
)# Generate unique files for all but Release
file(GENERATE
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/outFile-$<CONFIG>.txt
INPUT
${CMAKE_CURRENT_SOURCE_DIR}/input.txt.in
CONDITION
$<NOT:$<CONFIG:Release>>
)
# Embedded content, bracket syntax does not
# prevent the use of generator expressions
file(GENERATE
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/details-$<CONFIG>.txt
CONTENT [[
Built as "$<CONFIG>" for platform "$<PLATFORM_ID>".
Defines: $<TARGET_PROPERTY:COMPILE_DEFINITIONS>
]]
TARGET SomeTarget
)# Embedded content, bracket syntax does not
# prevent the use of generator expressions
file(GENERATE
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/details-$<CONFIG>.txt
CONTENT [[
Built as "$<CONFIG>" for platform "$<PLATFORM_ID>".
Defines: $<TARGET_PROPERTY:COMPILE_DEFINITIONS>
]]
TARGET SomeTarget
)
在上面的第一种情况下,input.txt.in在写入输出文件时将评估文件中的任何生成器表达式。这有点类似于替换 CMake 变量的方式configure_file(),只不过这次替换的是生成器表达式。第二种情况演示了括号语法如何成为一种特别方便的内联定义文件内容的方法,即使涉及生成器表达式和引用也是如此。
In the first case above, any generator expressions in the input.txt.in file
will be evaluated when writing the output file.
This is somewhat analogous to the way configure_file() substitutes CMake
variables, except this time the substitution is for generator expressions.
The second case demonstrates how bracket syntax can be a particularly
convenient way of defining file contents inline, even when generator
expressions and quoting are involved.
通常,每种构建类型的输出文件都不同。然而,在某些情况下,可能希望输出文件始终相同,例如文件内容不依赖于构建类型而是依赖于某些其他生成器表达式。为了支持此类用例,CMake 允许不同构建类型的输出文件相同,但前提是生成的文件内容对于这些构建类型也相同。CMake 不允许多个file(GENERATE…)命令尝试生成相同的输出文件。
Usually, the output file would be different for each build type. In some
situations, however, it may be desirable for the output file to always be the
same, such as where the file contents do not depend on the build type but
rather on some other generator expressions. To support such use cases, CMake
allows the output file to be the same for different build types, but only if
the generated file contents are also identical for those build types. CMake
disallows multiple file(GENERATE…) commands trying to generate the same
output file.
与 一样file(COPY…),该file(GENERATE…)命令仅在内容实际更改时才会修改输出文件。因此,输出文件的时间戳也仅在内容不同时才会更新。当生成的文件用作构建目标(例如生成的头文件)中的输入时,这非常有用,因为它可以防止不必要的重建。
Like for file(COPY…), the file(GENERATE…) command will only modify the
output file if the contents actually change. Therefore, the output file’s
timestamp will also only be updated if the contents differ. This is useful when
the generated file is used as an input in a build target, such as a generated
header file, since it can prevent unnecessary rebuilds.
file(GENERATE…)与大多数其他 CMake 命令相比,其行为方式存在一些重要差异。由于它计算生成器表达式,因此它无法立即写出文件。相反,这些文件是作为生成阶段的一部分写入的,该生成阶段发生在
CMakeLists.txt处理完所有文件之后。这意味着file(GENERATE…)命令返回时生成的文件将不存在,因此这些文件在配置阶段不能用作其他内容的输入。特别是,由于生成的文件直到配置阶段结束才存在,因此无法使用configure_file()、file(COPY…)等复制或读取它们。但是,它们仍然可以用作构建阶段的输入,例如生成的源或标头。
There are some important differences in the way file(GENERATE…) behaves
compared to most other CMake commands. Because it evaluates generator
expressions, it cannot write out the files immediately. Instead, the files are
written as part of the generation phase, which occurs after all of the
CMakeLists.txt files have been processed. This means that the generated files
won’t exist when the file(GENERATE…) command returns, so the files cannot
be used as inputs to something else during the configure phase. In particular,
since the generated files won’t exist until the end of the configure phase,
they cannot be copied or read with configure_file(), file(COPY…), etc.
They can, however, still be used as inputs for the build phase, such as
generated sources or headers.
另一点需要注意的是,在 CMake 3.10 之前,file(GENERATE…)
相对路径的处理方式与通常的 CMake 约定不同。相对路径的行为未指定,通常最终与cmake调用时的工作目录相关。这是不可靠且不一致的,因此在 CMake 3.10 中,行为已更改为
INPUT相对于当前源目录和OUTPUT相对于当前二进制目录,就像处理路径的大多数其他 CMake 命令一样。项目应考虑使用相对路径不安全,
file(GENERATE…)除非最低 CMake 版本设置为 3.10 或更高版本。
Another point to note is that before CMake 3.10, file(GENERATE…)
handled relative paths differently compared to usual CMake conventions. The
behavior of relative paths was left unspecified and usually ended up being
relative to the working directory of when cmake was invoked. This was
unreliable and inconsistent, so in CMake 3.10 the behavior was changed to make
INPUT act as relative to the current source directory and OUTPUT relative
to the current binary directory, just like most other CMake commands that
handle paths. Projects should consider relative paths unsafe to use with
file(GENERATE…) unless the minimum CMake version is set to 3.10 or later.
该file()命令不仅可以复制或创建文件,还可以用于读取文件的内容:
The file() command can not only copy or create files, it can also be used to
read in a file’s contents:
file(READ fileName outVar
[OFFSET offset] [LIMIT byteCount] [HEX]
)file(READ fileName outVar
[OFFSET offset] [LIMIT byteCount] [HEX]
)
如果没有任何可选关键字,此命令将读取 的所有内容
fileName并将它们作为单个字符串存储在 中outVar。该OFFSET选项可用于仅从指定的偏移量读取,从文件开头以字节为单位计算。还可以使用该选项限制要读取的最大字节数LIMIT。如果HEX给出该选项,内容将转换为十六进制表示形式,这对于包含二进制数据而不是文本的文件非常有用。
Without any of the optional keywords, this command reads all the contents of
fileName and stores them as a single string in outVar. The OFFSET option
can be used to read only from the offset specified, counted in bytes from the
beginning of the file. The maximum number of bytes to read can also be limited
with the LIMIT option. If the HEX option is given, the contents will be
converted to a hexadecimal representation, which can be useful for files
containing binary data rather than text.
如果更希望逐行分解文件内容,子
STRINGS命令可能会更方便。它将整个文件的内容存储为一个列表,而不是将整个文件的内容存储为单个字符串,其中每一行都是一个列表项。
If it is more desirable to break up the file contents line-by-line, the
STRINGS sub-command may be more convenient.
Instead of storing the entire file’s contents as a single string, it stores
them as a list, with each line being one list item.
file(STRINGS fileName outVar
[LENGTH_MAXIMUM maxBytesPerLine]
[LENGTH_MINIMUM minBytesPerLine]
[LIMIT_INPUT maxReadBytes]
[LIMIT_OUTPUT maxStoredBytes]
[LIMIT_COUNT maxStoredLines]
[REGEX regex]
# ... other less commonly used options not shown
)file(STRINGS fileName outVar
[LENGTH_MAXIMUM maxBytesPerLine]
[LENGTH_MINIMUM minBytesPerLine]
[LIMIT_INPUT maxReadBytes]
[LIMIT_OUTPUT maxStoredBytes]
[LIMIT_COUNT maxStoredLines]
[REGEX regex]
# ... other less commonly used options not shown
)
和选项可LENGTH_MAXIMUM用于LENGTH_MINIMUM分别排除长于或短于特定字节数的字符串。使用 可以限制读取的总字节数LIMIT_INPUT,使用 可以限制存储的总字节数LIMIT_OUTPUT。然而,也许更有用的是限制存储的行LIMIT_COUNT总数而不是字节数的选项。
The LENGTH_MAXIMUM and LENGTH_MINIMUM options can be used to exclude
strings longer or shorter than a certain number of bytes respectively. The
total number of bytes read can be limited using LIMIT_INPUT, while the total
number of bytes stored can be limited using LIMIT_OUTPUT. Perhaps more likely
to be useful, however, is the LIMIT_COUNT option which limits the total
number of lines stored rather than the number of bytes.
该REGEX选项是从文件中仅提取特定行的有用方法。例如,以下内容获取一个列表,其中myStory.txt包含包含PKG_VERSION或 的所有行MODULE_VERSION。
The REGEX option is a useful way to extract only specific lines from a file.
For example, the following obtains a list with all lines in myStory.txt that
contain either PKG_VERSION or MODULE_VERSION.
file(STRINGS myStory.txt versionLines
REGEX "(PKG|MODULE)_VERSION"
)file(STRINGS myStory.txt versionLines
REGEX "(PKG|MODULE)_VERSION"
)
也可以将其组合起来LIMIT_COUNT以获得第一个匹配项。以下示例演示如何组合file()和string()提取与正则表达式匹配的第一行的一部分。
It can also be combined with LIMIT_COUNT to obtain just the first match. The
following example shows how to combine file() and string() to extract a
portion of the first line matching a regular expression.
set(regex "^ *FOO_VERSION *= *([^ ]+) *$")
file(STRINGS config.txt fooVersion
REGEX "${regex}"
)
string(REGEX REPLACE "${regex}" "\\1"
fooVersion "${fooVersion}"
)set(regex "^ *FOO_VERSION *= *([^ ]+) *$")
file(STRINGS config.txt fooVersion
REGEX "${regex}"
)
string(REGEX REPLACE "${regex}" "\\1"
fooVersion "${fooVersion}"
)
如果config.txt包含这样的行:
If config.txt contained a line like this:
FOO_VERSION = 2.3.5FOO_VERSION = 2.3.5
那么存储的值fooVersion将是2.3.5。
Then the value stored in fooVersion would be 2.3.5.
除了读写文件之外,CMake还支持其他常见的文件系统操作。
In addition to reading and writing files, CMake also supports other common file system operations.
file(MAKE_DIRECTORY dirs...)
file(REMOVE files...)
file(REMOVE_RECURSE filesOrDirs...)
file(RENAME source destination
# CMake 3.21 or later required for these options
[RESULT outVar]
[NO_REPLACE]
)file(MAKE_DIRECTORY dirs...)
file(REMOVE files...)
file(REMOVE_RECURSE filesOrDirs...)
file(RENAME source destination
# CMake 3.21 or later required for these options
[RESULT outVar]
[NO_REPLACE]
)
该MAKE_DIRECTORY子命令将确保列出的目录存在。根据需要创建中间路径,如果目录已存在,则不会报告错误。
The MAKE_DIRECTORY sub-command will ensure the listed directories exist.
Intermediate paths are created as necessary and no error is reported if a
directory already exists.
该REMOVE子命令可用于删除文件。如果列出的任何文件不存在,该file()命令不会报告错误。尝试使用子命令删除目录REMOVE将不会有任何效果。要删除目录及其所有内容,请改用REMOVE_RECURSE
子命令。
The REMOVE sub-command can be used to delete files.
If any of the listed files do not exist, the file() command does not report
an error.
Attempting to delete a directory with the REMOVE sub-command will have no
effect.
To delete directories and all of their contents, use the REMOVE_RECURSE
sub-command instead.
子RENAME命令重命名文件或目录。和必须source是destination同一类型,即两个文件或两个目录。不允许将文件指定为source以及 的现有目录destination。要将文件移动到目录中,必须将文件名指定为
destination. 此外,任何路径部分都destination必须已经存在——该
RENAME表单不会创建中间目录。
The RENAME sub-command renames a file or directory.
The source and destination must be the same type, i.e. both files or both
directories.
It is not permitted to specify a file as the source and an existing directory
for the destination.
To move a file into a directory, the file name must be specified as part of the
destination.
Furthermore, any path part of the destination must already exist — the
RENAME form will not create intermediate directories.
通常,file(RENAME)如果遇到任何错误,就会停止。在 CMake 3.21 或更高版本中,如果RESULT给出了关键字,则命令的结果将存储在命名变量中。这允许处理继续并从错误中恢复。成功时存储的值outVar将为 0,否则会出现错误消息。请注意,如果destination已经存在,则除非
NO_REPLACE给出(需要 CMake 3.21 或更高版本),否则不会将其视为错误。没有的话NO_REPLACE, thedestination就会默默地被 the 取代
source。
Ordinarily, file(RENAME) will halt if any error is encountered.
With CMake 3.21 or later, if the RESULT keyword is given, the outcome of the
command is stored in the named variable instead.
This allows processing to continue and recover from an error.
The value stored in outVar will be 0 upon success or an error message
otherwise.
Note that if destination already exists, it is not considered an error unless
NO_REPLACE is given (CMake 3.21 or later required).
Without NO_REPLACE, the destination will be silently replaced by the
source.
# Requires CMake 3.21 or later
file(RENAME someFile toSomethingElse
RESULT result
NO_REPLACE
)
if(result)
message(WARNING "File rename failed, taking action")
# ... handle failure to rename the file
endif()# Requires CMake 3.21 or later
file(RENAME someFile toSomethingElse
RESULT result
NO_REPLACE
)
if(result)
message(WARNING "File rename failed, taking action")
# ... handle failure to rename the file
endif()
CMake 的命令模式还支持一组非常相似的功能,可以在构建时而不是配置时使用:
CMake’s command mode also supports a very similar set of capabilities which can be used at build time rather than configure time:
cmake -E make_directory 目录... cmake -E 删除 [-f] 文件... # 从 CMake 3.17 中弃用 cmake -E remove_directory dir # 从 CMake 3.17 中弃用 cmake -E rm [-rRf] 文件或目录... cmake -E 重命名源目标
cmake -E make_directory dirs... cmake -E remove [-f] files... # deprecated from CMake 3.17 cmake -E remove_directory dir # deprecated from CMake 3.17 cmake -E rm [-rRf] filesOrDirs... cmake -E rename source destination
这些命令的行为方式基本上与基于 的对应命令类似file(),只有细微的变化。该remove_directory命令严格只能用于单个目录,但
file(REMOVE_RECURSE…)可以删除多个项目,并且可以列出文件和目录。该remove命令接受一个可选-f标志,该标志旨在在尝试删除不存在的文件时更改行为。该标志的记录行为是,如果没有-f,则返回非零退出代码,而如果有-f,将返回零退出代码。这是为了模仿 Unix 命令行为的各个方面rm -f。不幸的是,由于实现中长期存在的错误,这不是实际行为,并且退出代码cmake -E remove应该被认为是不可靠的,无论有没有-f标志。该命令是在 CMake 3.17 中添加的,作为
和命令rm的替代。它修复了退出代码错误,并且与 Unix 命令更加一致
。removeremove_directoryrm
These commands largely behave in a comparable way to their file()-based
counterparts, with only slight variations. The remove_directory command can
strictly only be used with a single directory, whereas
file(REMOVE_RECURSE…) can remove multiple items and both files and
directories can be listed.
The remove command accepts an optional -f flag which is intended to change
the behavior when an attempt is made to remove a file that does not exist.
The documented behavior of this flag is that without -f, a non-zero exit code
is returned, whereas with -f, a zero exit code will be returned.
This is intended to mimic aspects of the behavior of the Unix rm -f command.
Unfortunately, due to a long-standing bug in the implementation, this isn’t
the actual behavior and the exit code of cmake -E remove should be considered
unreliable, with or without the -f flag.
The rm command was added in CMake 3.17 as a replacement for both the remove
and remove_directory commands.
It fixes the exit code bug and is more closely aligned with the Unix rm
command.
CMake 3.14 添加了两个新的file()子命令,使项目能够查询和操作文件系统链接:
CMake 3.14 added two new file() sub-commands which enable the project to
query and manipulate file system links:
file(READ_SYMLINK linkName outVar)
file(CREATE_LINK pointedTo linkName
[RESULT outVar]
[COPY_ON_ERROR]
[SYMBOLIC]
)file(READ_SYMLINK linkName outVar)
file(CREATE_LINK pointedTo linkName
[RESULT outVar]
[COPY_ON_ERROR]
[SYMBOLIC]
)
子命令给出了指向READ_SYMLINK的路径。linkName请注意,符号链接通常使用相对路径,并且存储的值outVar
将只是这种情况下的原始相对路径。
The READ_SYMLINK sub-command gives the path of what linkName points to.
Note that symlinks often use relative paths and the value stored in outVar
will just be the raw relative path for such cases.
该CREATE_LINK命令允许项目创建硬链接或符号链接。默认情况下会创建硬链接,但SYMBOLIC可以选择创建符号链接。在大多数情况下,SYMBOLIC会推荐,因为它支持更多场景(例如跨不同文件系统的链接)。该RESULT关键字可用于命名一个变量来存储运算结果。成功时存储的值将为 0,否则会显示错误消息。如果没有该RESULT选项,失败将导致 CMake 因致命错误而停止。该COPY_ON_ERROR选项提供了当尝试创建链接失败时的后备措施,将操作降级为复制pointedTo到linkName. 它的存在主要是为了允许在不支持创建链接的情况下使用该命令,例如到不同驱动器或设备上的路径的硬链接。
The CREATE_LINK command allows the project to create hard or symbolic links.
Hard links are created by default, but the SYMBOLIC option can be given to
create a symbolic link instead.
In most situations, SYMBOLIC would be recommended since it supports more
scenarios (e.g. linking across different file systems).
The RESULT keyword can be used to name a variable in which to store the
result of the operation.
The value stored will be 0 upon success or an error message otherwise.
Without the RESULT option, a failure will cause CMake to halt with a
fatal error.
The COPY_ON_ERROR option provides a fallback for when an attempt to create
a link fails, downgrading the operation to copy pointedTo to linkName.
It exists primarily to allow the command to be used where creation of links
is not supported, such as a hard link to a path on a different drive or device.
所有 CMake 版本都允许使用 CMake 的命令模式基本创建符号链接(无法通过此方法创建硬链接):
All CMake versions allow basic creation of symbolic links using CMake’s command mode too (hard links cannot be created via this method):
cmake -E create_symlink 指向链接名称
cmake -E create_symlink pointedTo linkName
CMake 3.14 还添加了查询文件大小的功能:
CMake 3.14 also added the ability to query the size of a file:
file(SIZE fileName outVar)file(SIZE fileName outVar)
指定的内容fileName必须存在,而且重要的是它还必须可读。
The specified fileName must exist and importantly it must also be readable.
CMake 3.19 添加了file()用于设置文件和目录权限的子命令:
CMake 3.19 added file() sub-commands for setting file and directory
permissions:
file(CHMOD | CHMOD_RECURSE
files... directories...
[PERMISSIONS permissions...]
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
)file(CHMOD | CHMOD_RECURSE
files... directories...
[PERMISSIONS permissions...]
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
)
和子命令的行为相同,只是后者也会递归地下降到子目录中CHMOD。CHMOD_RECURSE支持的值与子命令
permissions支持的值相同
。或仅适用于其各自类型的实体,并且它们将覆盖该实体类型。人们可以仅指定两种更具体的类型之一来仅对该类型的实体进行操作。下面展示了如何利用此功能仅设置目录权限并保持文件权限不变:file(COPY)FILE_PERMISSIONSDIRECTORY_PERMISSIONSPERMISSIONS
The CHMOD and CHMOD_RECURSE sub-commands are identical in their behavior
except that the latter will also descend recursively into subdirectories.
The supported values for permissions are the same as those supported by the
file(COPY) sub-command.
FILE_PERMISSIONS or DIRECTORY_PERMISSIONS will apply only to their
respective type of entity and they will override PERMISSIONS for that entity
type.
One can specify just one of the two more specific types to operate on just
that type of entity.
The following shows how to take advantage of this to set permissions just for
directories and leave file permissions unmodified:
file(CHMOD_RECURSE ${someFilesAndDirs}
DIRECTORY_PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)file(CHMOD_RECURSE ${someFilesAndDirs}
DIRECTORY_PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
CMake 还支持使用递归或非递归形式的通配来列出一个或多个目录的内容:
CMake also supports listing the contents of one or more directories with either a recursive or non-recursive form of globbing:
file(GLOB outVar
[LIST_DIRECTORIES true|false]
[RELATIVE path]
[CONFIGURE_DEPENDS] # Requires CMake 3.12 or later
expressions...
)file(GLOB outVar
[LIST_DIRECTORIES true|false]
[RELATIVE path]
[CONFIGURE_DEPENDS] # Requires CMake 3.12 or later
expressions...
)
file(GLOB_RECURSE outVar
[LIST_DIRECTORIES true|false]
[RELATIVE path]
[FOLLOW_SYMLINKS]
[CONFIGURE_DEPENDS] # Requires CMake 3.12 or later
expressions...
)file(GLOB_RECURSE outVar
[LIST_DIRECTORIES true|false]
[RELATIVE path]
[FOLLOW_SYMLINKS]
[CONFIGURE_DEPENDS] # Requires CMake 3.12 or later
expressions...
)
这些命令查找名称与所提供的任何名称匹配的所有文件
expressions,可以将其视为简化的正则表达式。通过添加字符子集选择,可能更容易将它们视为普通通配符。对于GLOB_RECURSE,它们还可以包含路径组件。
These commands find all files whose names match any of the provided
expressions, which can be thought of as simplified regular expressions. It
may be easier to think of them as ordinary wildcards with the addition of
character subset selection. For GLOB_RECURSE, they can also include path
components.
一些示例应该有助于阐明基本用法:
Some examples should help clarify basic use:
|
所有名称以 All files whose name ends with |
|
Files like |
|
匹配所有具有以下形式的文件: Matches all files of the form |
|
对于 For |
对于GLOB,与表达式匹配的文件和目录都存储在 中
outVar。另一方面,对于GLOB_RECURSE,默认情况下不包含目录名称,但这可以通过选项进行控制LIST_DIRECTORIES
。此外,对于GLOB_RECURSE,目录的符号链接通常被报告为条目outVar而不是下降到其中,但该
FOLLOW_SYMLINKS选项指示 CMake 下降到目录而不是列出它。
For GLOB, both files and directories matching the expression are stored in
outVar. For GLOB_RECURSE, on the other hand, directory names are not
included by default but this can be controlled with the LIST_DIRECTORIES
option. Furthermore, for GLOB_RECURSE, symlinks to directories are normally
reported as entries in outVar rather than descending into them, but the
FOLLOW_SYMLINKS option directs CMake to descend into the directory instead of
listing it.
默认情况下,无论使用什么表达式,返回的文件名集都将是完整的绝对路径。该RELATIVE选项可用于更改此行为,以便报告的路径相对于特定目录。
The set of file names returned will be full absolute paths by default,
regardless of the expressions used. The RELATIVE option can be used to change
this behavior such that the reported paths are relative to a specific
directory.
set(base /usr/share)
file(GLOB_RECURSE images
RELATIVE ${base}
${base}/*/*.png
)set(base /usr/share)
file(GLOB_RECURSE images
RELATIVE ${base}
${base}/*/*.png
)
上面的代码将找到下面的所有图像/usr/share,并包括这些图像的路径,除了/usr/share被剥离的部分。请注意/*/表达式中的 ,以允许匹配基点以下的任何目录。
The above will find all images below /usr/share and include the path to those
images, except with the /usr/share part stripped off. Note the /*/ in the
expression to allow any directory below the base point to be matched.
开发人员应该意识到这些file(GLOB…)命令并不像 Unix findshell 命令那么快。因此,如果使用它来搜索文件系统中包含许多文件的部分,则运行时间可能会很重要。
Developers should be aware that the file(GLOB…) commands are not as fast
as, say, the Unix find shell command. Therefore, run time can be non-trivial
if using it to search parts of the file system that contain many files.
该file()命令还有许多其他形式来执行不同的任务。一对令人惊讶的强大子命令使项目能够从任意 URL 下载文件或将文件上传到任意 URL。
The file() command has a number of other forms which carry out different
tasks.
A surprisingly powerful pair of sub-commands gives projects the ability to
download files from and upload files to an arbitrary URL.
file(DOWNLOAD url fileName [options...])
file(UPLOAD fileName url [options...])file(DOWNLOAD url fileName [options...])
file(UPLOAD fileName url [options...])
该DOWNLOAD表单从指定位置下载文件url并将其保存到
fileName. fileName如果给出相对值,则将其解释为相对于当前二进制目录。CMake 3.19 及更高版本允许fileName省略 ,在这种情况下,文件将被下载但被丢弃。这可用于检查 URL 是否存在,而无需将文件保存在任何地方(不建议用于预计很大的文件)。
The DOWNLOAD form downloads a file from the specified url and saves it to
fileName.
If a relative fileName is given, it is interpreted as being relative to the
current binary directory.
CMake 3.19 and later allow the fileName to be omitted, in which case the
file is downloaded but discarded.
This can be used to check that a URL exists without having to save the file
anywhere (not recommended for files that are expected to be large).
该UPLOAD表单执行补充操作,将指定的文件上传到指定的 url。对于上传,相对路径被解释为相对于当前源目录。
The UPLOAD form performs the complementary operation, uploading the named
file to the specified url.
For uploads, a relative path is interpreted as being relative to the current
source directory.
可以使用以下选项,其中大多数选项对于 和 都是通用DOWNLOAD
的UPLOAD:
The following options can be used, most of which are common to both DOWNLOAD
and UPLOAD:
LOG outVar
LOG outVar
SHOW_PROGRESS
SHOW_PROGRESS
TIMEOUT seconds
TIMEOUT seconds
seconds。
seconds have elapsed.
INACTIVITY_TIMEOUT seconds
INACTIVITY_TIMEOUT seconds
INACTIVITY_TIMEOUT选项提供了此功能,但TIMEOUT仅允许限制总时间。
INACTIVITY_TIMEOUT option provides this
capability, whereas TIMEOUT only allows the total time to be limited.
TLS_VERIFY value
TLS_VERIFY value
https://。如果未提供此选项,CMake 会查找名为的变量
CMAKE_TLS_VERIFY。如果选项和变量均未定义,则默认行为是不验证服务器证书。请注意,UPLOAD仅在 CMake 3.18 中添加了对此选项的支持。
https:// url.
If this option is not provided, CMake looks for a variable named
CMAKE_TLS_VERIFY instead.
If neither the option nor the variable are defined, the default behavior is to
not verify the server certificate.
Note that UPLOAD support for this option was only added in CMake 3.18.
TLS_CAINFO fileName
TLS_CAINFO fileName
https://网址。如果未提供此选项,CMake 会查找名为的变量
CMAKE_TLS_CAINFO。请注意,UPLOAD仅在 CMake 3.18 中添加了对此选项的支持。
https:// urls.
If this option is not provided, CMake looks for a variable named
CMAKE_TLS_CAINFO instead.
Note that UPLOAD support for this option was only added in CMake 3.18.
EXPECTED_HASH ALGO=value
EXPECTED_HASH ALGO=value
DOWNLOAD. 它指定正在下载的文件的校验和,以便 CMake 可以验证内容。
ALGO可以是 CMake 支持的任何一种哈希算法,最常用的是MD5和SHA1。一些较旧的项目可能会使用 的EXPECTED_MD5替代形式
EXPECTED_HASH MD5=…,但新项目应该更喜欢这种EXPECTED_HASH
形式。
DOWNLOAD.
It specifies the checksum of the file being downloaded so that CMake can verify
the contents.
ALGO can be any one of the hashing algorithms CMake supports, the most
commonly used being MD5 and SHA1.
Some older projects may use EXPECTED_MD5 as an alternative to
EXPECTED_HASH MD5=…, but new projects should prefer the EXPECTED_HASH
form.
对于 CMake 3.7 或更高版本,以下选项也可用于
DOWNLOAD和UPLOAD:
With CMake 3.7 or later, the following options are also available for both
DOWNLOAD and UPLOAD:
USERPWD username:password
USERPWD username:password
HTTPHEADER header
HTTPHEADER header
file(DOWNLOAD
"https://bkt.s3.amazonaws.com/myfile.tar.gz"
myfile.tar.gz
EXPECTED_HASH
SHA1=${myfileHash}
HTTPHEADER
"Host: bkt.s3.amazonaws.com"
HTTPHEADER
"Date: ${timestamp}"
HTTPHEADER
"Content-Type: application/x-compressed-tar"
HTTPHEADER
"Authorization: AWS ${s3key}:${signature}"
)
file(DOWNLOAD
"https://bkt.s3.amazonaws.com/myfile.tar.gz"
myfile.tar.gz
EXPECTED_HASH
SHA1=${myfileHash}
HTTPHEADER
"Host: bkt.s3.amazonaws.com"
HTTPHEADER
"Date: ${timestamp}"
HTTPHEADER
"Content-Type: application/x-compressed-tar"
HTTPHEADER
"Authorization: AWS ${s3key}:${signature}"
)
基于file()的下载和上传命令往往更多地用作安装步骤、打包或测试报告的一部分,但它们偶尔也可以用于其他目的。示例包括在配置时下载引导文件或将无法或不应存储为项目源的一部分的文件带入构建(例如,仅应由某些开发人员访问的敏感文件、非常大的文件等) 。后面的章节提供了使用这些命令产生巨大效果的具体场景。
The file()-based download and upload commands tend to find use more as part
of install steps, packaging or test reporting, but they can also occasionally
find use for other purposes. Examples include things like downloading bootstrap
files at configure time or bringing a file into the build which cannot or
should not be stored as part of the project sources (e.g. sensitive files that
should only be accessible for certain developers, very large files, etc.).
Later chapters provide specific scenarios where these commands are used with
great effect.
本章介绍了一系列与文件处理相关的 CMake 功能。可以非常有效地使用各种方法以独立于平台的方式执行一系列任务,但它们也可能被滥用。建立良好的模式并在整个项目中一致应用它们将有助于确保新开发人员接触到更好的实践。
A range of CMake functionality related to file handling has been presented in this chapter. The various methods can be used very effectively to carry out a range of tasks in a platform independent way, but they can also be misused. Establishing good patterns and applying them consistently throughout a project will help ensure new developers are exposed to better practices.
cmake_path()如果项目的最低 CMake 版本为 3.20 或更高版本,请考虑使用该命令进行路径处理。get_filename_component()它通常使用比更旧的命令的类似功能更一致的语法file()。另一方面,cmake_path()不访问底层文件系统,因此如果需要解析符号链接,则无法使用它。
Consider using the cmake_path() command for path handling if the project’s
minimum CMake version is 3.20 or later.
It generally uses a more consistent syntax than analogous capabilities of the
much older get_filename_component() and file() commands.
On the other hand, cmake_path() does not access the underlying file system,
so it cannot be used if resolving symbolic links is required.
请注意,if(IS_ABSOLUTE)和cmake_path(IS_ABSOLUTE)命令根据主机平台解释路径,但两者之间存在细微差别。在某些情况下,它们可能会为同一路径产生不同的结果。
cmake_path(IS_ABSOLUTE)遵循C++函数的实现
std::filesystem::path::is_absolute(),而if(IS_ABSOLUTE)
使用自己的逻辑来处理一些特殊情况。从 Windows 主机交叉编译到非 Windows 目标平台时要格外小心,反之亦然。如果任一命令使用不当,某些路径可能会产生未定义的行为或给出与直观预期值相反的结果。
Note that the if(IS_ABSOLUTE) and cmake_path(IS_ABSOLUTE) commands
interpret the path based on the host platform, but there are subtle
differences between the two.
They can yield different results for the same path in some cases.
cmake_path(IS_ABSOLUTE) follows the implementation of the C++
std::filesystem::path::is_absolute() function, whereas if(IS_ABSOLUTE)
uses its own logic with handling for some special cases.
Take extra care if cross-compiling from a Windows host to a non-Windows target
platform, or vice versa.
If either command is used inappropriately, some paths may yield undefined
behavior or give an opposite result to the value one may intuitively expect.
该configure_file()命令是新开发人员经常忽略的命令,但它是提供文件的关键方法,该文件的内容可以根据配置时确定的变量进行定制,甚至只是进行简单的文件复制。常见的命名约定是源文件名部分和目标文件名部分相同,但源文件名部分附加了额外的内容.in。某些 IDE 环境理解此约定,并将根据不带后缀的文件扩展名在源文件上提供适当的语法突出显示.in。后缀的存在.in不仅可以清楚地提醒您该文件在使用前需要进行转换,而且还可以防止 CMake 或编译器在多个目录中查找文件时意外拾取该文件而不是目标文件。当目标是 C/C++ 标头并且当前源目录和二进制目录都位于标头搜索路径上时,这一点尤其重要。
The configure_file() command is one that new developers often overlook, yet
it is a key method of providing a file whose contents can be tailored according
to variables determined at configure time, or even just to do a simple file
copy. A common naming convention is for the file name part of the source and
destination to be the same, except the source has an extra .in appended.
Some IDE environments understand this convention and will provide
appropriate syntax highlighting on the source file based on the file’s
extension without the .in suffix. The presence of the .in suffix not only
serves as a clear reminder that the file needs to be transformed before
use, it also prevents it from being accidentally picked up instead of the
destination if CMake or the compiler look for files in multiple directories.
This is especially relevant when the destination is a C/C++ header and
the current source and binary directories are both on the header search path.
选择最合适的命令来复制文件并不总是很清楚。configure_file()在、file(COPY)和之间进行选择时,以下内容可以作为有用的指南
file(INSTALL):
Choosing the most appropriate command for copying files is not always clear.
The following may serve as a useful guide when choosing between
configure_file(), file(COPY) and file(INSTALL):
configure_file()这是最简洁的实现方法。
configure_file() is the most concise way to achieve it.
configure_file()比 稍短file(COPY…),但两者都适合。
configure_file() is slightly shorter than file(COPY…), but either would
be suitable.
file(COPY)
or命令。file(INSTALL)
file(COPY)
or file(INSTALL) command must be used.
file(COPY)或者file(INSTALL)如果项目的最低 CMake 版本为 3.19 或更早版本,则必须使用该控制。
file(COPY) or file(INSTALL) must be used if the project’s minimum
CMake version is 3.19 or earlier.
file(INSTALL)通常只应用作安装脚本的一部分。更喜欢file(COPY)其他情况。
file(INSTALL) should only typically be used as part of install scripts.
Prefer file(COPY) instead for other situations.
在 CMake 3.10 之前,file(GENERATE…)与 CMake 提供的大多数其他命令相比,该命令对相对路径的处理不同。项目不应依赖开发人员了解这种不同的行为,而应始终使用绝对路径指定INPUT和文件,以避免在意外位置生成错误或文件。OUTPUT
Prior to CMake 3.10, the file(GENERATE…) command had different handling of
relative paths compared to most other commands provided by CMake. Rather than
relying on developers being aware of this different behavior, projects should
instead prefer to always specify the INPUT and OUTPUT files with an
absolute path to avoid errors or files being generated in unexpected locations.
使用file(DOWNLOAD…)或
file(UPLOAD…)命令下载或上传文件时,应仔细考虑安全性和效率方面。尽量避免在项目源版本控制系统中存储的任何文件中嵌入任何类型的身份验证详细信息(用户名、密码、私钥等)。这些详细信息应该来自项目外部,例如通过环境变量(仍然有些不安全)、在用户文件系统上找到的具有适当权限限制访问的文件或某种钥匙串。下载时使用该EXPECTED_HASH选项可重复使用先前运行时下载的内容,并避免可能耗时的远程操作。如果无法提前知道下载文件的哈希值,则TLS_VERIFY强烈建议使用该选项以确保内容的完整性。还可以考虑指定
或两者TIMEOUT,INACTIVITY_TIMEOUT以防止配置运行在网络连接较差或不可靠时无限期阻塞。
When downloading or uploading files with the file(DOWNLOAD…) or
file(UPLOAD…) commands, security and efficiency aspects should be carefully
considered. Strive to avoid embedding any sort of authentication details
(usernames, passwords, private keys, etc.) in any file stored in a version
control system for the project’s sources. Such details should come from outside
the project, such as through environment variables (still somewhat insecure),
files found on the user’s file system with appropriate permissions limiting
access or a keychain of some kind. Make use of the EXPECTED_HASH option when
downloading to re-use previously downloaded content from an earlier run and
avoid a potentially time-consuming remote operation. If the downloaded file’s
hash cannot be known in advance, then the TLS_VERIFY option is highly
recommended to ensure the integrity of the content. Also consider specifying a
TIMEOUT, INACTIVITY_TIMEOUT or both to prevent a configure run from
blocking indefinitely if network connectivity is poor or unreliable.
版本控制是经常没有得到应有关注的事情之一。版本号向用户传达的信息的重要性常常被低估,导致用户无法满足期望或对版本之间的更改感到困惑。营销与版本控制策略如何影响构建、打包等技术实施之间也不可避免地存在紧张关系。尽早考虑和确定这些事情可以使项目在首次公开发布时处于更好的位置。本章探讨了实施有效版本控制策略的方法,利用 CMake 功能提供健壮、高效的流程。
Versioning is one of those things that frequently doesn’t get the attention it deserves. The importance of what a version number communicates to users is often underestimated, resulting in users with unmet expectations or confusion about changes between releases. There are also the inevitable tensions between marketing and how a versioning strategy affects the technical implementation of builds, packaging and so on. Thinking about and establishing these things early places the project in a better position when it comes time to deliver the first public release. This chapter explores ways to implement an effective versioning strategy, taking advantage of CMake features to provide a robust, efficient process.
项目版本通常需要在顶级
CMakeLists.txt文件的开头附近定义,以便构建的各个部分可以引用它。源代码可能希望嵌入项目版本,以便可以将其显示给用户或记录在日志文件中,打包步骤可能需要它来定义发布版本详细信息等等。人们可以简单地在文件开头附近设置一个变量,CMakeLists.txt以所需的任何形式记录版本号,如下所示:
A project version often needs to be defined near the beginning of the top level
CMakeLists.txt file so that various parts of the build can refer to it.
Source code may want to embed the project version so that it can be displayed
to the user or recorded in a log file, packaging steps may need it to define
release version details and so on. One could simply set a variable near the
start of the CMakeLists.txt file to record a version number in whatever form
is needed like so:
cmake_minimum_required(VERSION 3.0)
project(FooBar)
set(FooBar_VERSION 2.4.7)cmake_minimum_required(VERSION 3.0)
project(FooBar)
set(FooBar_VERSION 2.4.7)
如果需要提取单个组件,则可能需要定义一组稍微复杂的变量。一个示例可能如下所示:
If individual components need to be extracted, a slightly more involved set of variables may need to be defined. One example may look something like this:
cmake_minimum_required(VERSION 3.0)
project(FooBar)
set(FooBar_VERSION_MAJOR 2)
set(FooBar_VERSION_MINOR 4)
set(FooBar_VERSION_PATCH 7)
set(FooBar_VERSION
${FooBar_VERSION_MAJOR}.${FooBar_VERSION_MINOR}.${FooBar_VERSION_PATCH}
)cmake_minimum_required(VERSION 3.0)
project(FooBar)
set(FooBar_VERSION_MAJOR 2)
set(FooBar_VERSION_MINOR 4)
set(FooBar_VERSION_PATCH 7)
set(FooBar_VERSION
${FooBar_VERSION_MAJOR}.${FooBar_VERSION_MINOR}.${FooBar_VERSION_PATCH}
)
不同的项目可能使用不同的变量命名约定。版本号的结构也可能因项目而异,从而缺乏一致性,使得将许多项目作为更大集合或超级构建的一部分聚集在一起变得更加困难(在第29.1 节“超级构建结构”中讨论)。CMake 3.0 引入了新功能,使指定版本详细信息变得更加容易,并为项目版本编号带来一定的一致性。该VERSION关键字已添加到命令中,强制使用Major.minor.patch.tweakproject()形式的版本号
作为预期格式。根据该信息,将自动填充一组变量,以使完整版本字符串以及每个版本组件单独可供项目的其余部分使用。如果提供的版本字符串省略了某些部分(
例如,调整部分通常被省略),则相应的变量将保留为空。下表显示了当关键字与命令一起使用时自动填充的版本变量:VERSIONproject()
Different projects may use different conventions for the naming of variables.
The structure of version numbers can also vary from project to project, with
the resultant lack of consistency making it that much more difficult to bring
together many projects as part of a larger collection or superbuild (discussed
in Section 29.1, “Superbuild Structure”). CMake 3.0
introduced new functionality which makes specifying version details easier and
brings some consistency to project version numbering. The VERSION keyword was
added to the project() command, mandating a version number of the form
major.minor.patch.tweak as the expected format. From that information, a set
of variables are automatically populated to make the full version string as
well as each version component individually available to the rest of the
project. Where a version string is provided with some parts omitted (the
tweak part is often left out, for example), the corresponding variables are
left empty. The following table shows the automatically populated version
variables when the VERSION keyword is used with the project() command:
|
|
|
|
|
|
|
|
|
|
两组变量的用途略有不同。项目特定的projectName_…变量可用于获取当前目录范围或以下目录范围内任何位置的版本详细信息。像这样的调用
project(FooBar VERSION 2.7.3)会产生名为 的变量FooBar_VERSION,
FooBar_VERSION_MAJOR等等。由于对 的两次调用不能project()使用相同的projectName,因此这些项目特定的变量不会被对该project()命令的其他调用覆盖。PROJECT_…另一方面,变量每次调用时都会更新,project()因此它们可用于提供project()当前范围或以上范围内最近调用的版本详细信息。project()从 CMake 3.12 开始,一组类似的变量还提供了顶级文件中
调用设置的版本详细信息CMakeLists.txt。这些变量是:
The two sets of variables serve slightly different purposes. The
project-specific projectName_… variables can be used to obtain the version
details anywhere from the current directory scope or below. A call like
project(FooBar VERSION 2.7.3) results in variables named FooBar_VERSION,
FooBar_VERSION_MAJOR and so on. Since no two calls to project() can use the
same projectName, these project-specific variables won’t be overwritten by
other calls to the project() command. The PROJECT_… variables, on the
other hand, are updated every time project() is called, so they can be used
to provide the version details of the most recent call to project() in the
current scope or above. From CMake 3.12, an analogous set of variables also
provides the version details set by the project() call in the top level
CMakeLists.txt file. These variables are:
|
|
|
|
|
还遵循相同的模式来为项目名称、描述和主页 url 提供变量,后两者分别在 CMake 版本 3.9 和 3.12 中添加。作为一般指南,PROJECT_…变量对于通用代码(尤其是模块)非常有用,可以作为一种为打包或文档详细信息等内容定义合理默认值的方法。这些
CMAKE_PROJECT_…变量有时也用于默认值,但它们可能不太可靠,因为它们的使用通常假设一个特定的顶级项目。这些projectName_…变量是最稳健的,因为它们总是明确地提供哪个项目的详细信息。
This same pattern is also followed to provide variables for the project name,
description and homepage url, the latter two being added in CMake versions 3.9
and 3.12 respectively. As a general guide, the PROJECT_… variables can be
useful for generic code (especially modules) as a way to define sensible
defaults for things like packaging or documentation details. The
CMAKE_PROJECT_… variables are sometimes used for defaults too, but they can
be a bit less reliable since their use typically assumes a particular top level
project. The projectName_… variables are the most robust, since they are
always unambiguous in which project’s details they will provide.
在处理支持早于 3.0 的 CMake 版本的项目时,有时他们会定义自己的版本相关变量,这些变量与 CMake 3.0 及更高版本自动定义的变量发生冲突。这可能会导致CMP0048政策警告凸显冲突。下面显示了导致此类警告的代码示例:
When working with projects that support CMake versions earlier than 3.0, it is
sometimes the case that they will define their own version-related variables
which clash with those automatically defined by CMake 3.0 and later. This can
lead to CMP0048 policy warnings which highlight the conflict. The following
shows an example of code which leads to such a warning:
cmake_minimum_required(VERSION 2.8.12)
set(FooBar_VERSION 2.4.7)
project(FooBar)cmake_minimum_required(VERSION 2.8.12)
set(FooBar_VERSION 2.4.7)
project(FooBar)
在上面,显式设置了变量,但该变量名称与命令自动定义的FooBar_VERSION变量冲突。project()由此产生的策略警告旨在鼓励项目使用不同的变量名称或更新到最低 CMake 版本 3.0 并在命令中设置版本详细信息
project()。
In the above, the FooBar_VERSION variable is explicitly set, but this
variable name conflicts with the variable that the project() command would
automatically define. The resultant policy warning is intended as an
encouragement for the project to either use a different variable name or to
update to a minimum CMake version of 3.0 and set the version details in the
project() command instead.
一旦在文件中定义了版本详细信息CMakeLists.txt,一个非常常见的需求是使它们可用于项目编译的源代码。可以使用多种不同的方法,每种方法都有自己的优点和缺点。CMake 新手最常用的技术之一是在项目的顶层添加编译器定义:
Once the version details are defined in the CMakeLists.txt file, a very
common need is to make them available to source code compiled by the project. A
number of different approaches can be used, each with their own strengths and
weaknesses. One of the most common techniques used by those new to CMake is to
add a compiler define at the top level of the project:
cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
add_definitions(-DFOOBAR_VERSION=\"${FooBar_VERSION}\")cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
add_definitions(-DFOOBAR_VERSION=\"${FooBar_VERSION}\")
这使得版本可以作为原始字符串使用,如下所示:
This makes the version available as a raw string able to be used like so:
void printVersion()
{
std::cout << FOOBAR_VERSION << std::endl;
}void printVersion()
{
std::cout << FOOBAR_VERSION << std::endl;
}
虽然这种方法相当简单,但将定义添加到项目中每个文件的编译中会带来一些缺点。除了使每个要编译的文件的命令行变得混乱之外,这意味着每当版本号发生变化时,整个项目都会被重建。这看起来似乎是一个小问题,但是经常在源代码控制系统中的不同分支之间切换的开发人员几乎肯定会对所有不必要的重新编译感到非常恼火。稍微好一点的方法是使用源属性FOOBAR_VERSION仅为需要的文件定义符号。例如:
While this approach is fairly simple, adding the definition to the compilation
of every single file in the project comes with some drawbacks. Apart from
cluttering up the command line of every file to be compiled, it means that any
time the version number changes, the whole project gets rebuilt. This may seem
like a minor point, but developers who regularly switch between different
branches in a source control system will almost certainly get very annoyed by
all the unnecessary recompilations. A slightly better approach uses source
properties to define the FOOBAR_VERSION symbol only for those files where it
is needed. For example:
cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
add_executable(FooBar main.cpp src1.cpp src2.cpp ...)
get_source_file_property(defs src1.cpp COMPILE_DEFINITIONS)
list(APPEND defs "FOOBAR_VERSION=\"${FooBar_VERSION}\"")
set_source_files_properties(src1.cpp PROPERTIES
COMPILE_DEFINITIONS "${defs}"
)cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
add_executable(FooBar main.cpp src1.cpp src2.cpp ...)
get_source_file_property(defs src1.cpp COMPILE_DEFINITIONS)
list(APPEND defs "FOOBAR_VERSION=\"${FooBar_VERSION}\"")
set_source_files_properties(src1.cpp PROPERTIES
COMPILE_DEFINITIONS "${defs}"
)
这避免了将编译器定义添加到每个文件中,而是仅将其添加到需要它的文件中。然而,如第 9.5 节“源属性”中所述,设置各个源属性时可能会对构建依赖关系产生负面影响,并且这些再次导致重建的文件数量超出了必要的数量。因此,这种方法可能看起来像是一种改进,但通常情况并非如此。
This avoids adding the compiler definition to every file, instead only adding it to those files that need it. As mentioned in Section 9.5, “Source Properties”, however, there can be negative impacts on the build dependencies when setting individual source properties and these once again result in more files being rebuilt than should be necessary. Therefore, this approach may seem like an improvement, but often it won’t be.
configure_file()另一种常见的方法是编写提供版本详细信息的头文件,而不是在命令行上传递版本详细信息。例如:
Rather than passing the version details on the command line, another common
approach is to use configure_file() to write a header file that supplies
the version details. For example:
#include <string>
inline std::string getFooBarVersion()
{
return "@FooBar_VERSION@";
}
inline unsigned getFooBarVersionMajor()
{
return @FooBar_VERSION_MAJOR@;
}
inline unsigned getFooBarVersionMinor()
{
return @FooBar_VERSION_MINOR@ +0;
}
inline unsigned getFooBarVersionPatch()
{
return @FooBar_VERSION_PATCH@ +0;
}
inline unsigned getFooBarVersionTweak()
{
return @FooBar_VERSION_TWEAK@ +0;
}#include <string>
inline std::string getFooBarVersion()
{
return "@FooBar_VERSION@";
}
inline unsigned getFooBarVersionMajor()
{
return @FooBar_VERSION_MAJOR@;
}
inline unsigned getFooBarVersionMinor()
{
return @FooBar_VERSION_MINOR@ +0;
}
inline unsigned getFooBarVersionPatch()
{
return @FooBar_VERSION_PATCH@ +0;
}
inline unsigned getFooBarVersionTweak()
{
return @FooBar_VERSION_TWEAK@ +0;
}
#include "foobar_version.h"
#include <iostream>
int main(int argc, char* argv[])
{
std::cout
<< "VERSION = " << getFooBarVersion() << "\n"
<< "MAJOR = " << getFooBarVersionMajor() << "\n"
<< "MINOR = " << getFooBarVersionMinor() << "\n"
<< "PATCH = " << getFooBarVersionPatch() << "\n"
<< "TWEAK = " << getFooBarVersionTweak()
<< std::endl;
return 0;
}#include "foobar_version.h"
#include <iostream>
int main(int argc, char* argv[])
{
std::cout
<< "VERSION = " << getFooBarVersion() << "\n"
<< "MAJOR = " << getFooBarVersionMajor() << "\n"
<< "MINOR = " << getFooBarVersionMinor() << "\n"
<< "PATCH = " << getFooBarVersionPatch() << "\n"
<< "TWEAK = " << getFooBarVersionTweak()
<< std::endl;
return 0;
}
cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
configure_file(foobar_version.h.in foobar_version.h @ONLY)
add_executable(FooBar main.cpp)
target_include_directories(FooBar PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
configure_file(foobar_version.h.in foobar_version.h @ONLY)
add_executable(FooBar main.cpp)
target_include_directories(FooBar PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
in对于minor、patch和
tweak+0部分是必需的,以便在省略这些版本组件的情况下允许其相应的变量为空。foobar_version.h.in
The +0 in foobar_version.h.in is necessary for the minor, patch and
tweak parts to allow their corresponding variables to be empty in the case of
those version components being omitted.
通过像这样的标头提供版本详细信息是对以前技术的改进。版本详细信息不包含在任何源文件编译的命令行中,只有当版本详细信息更改时标头才会重新编译#include的
那些文件。foobar_version.h提供所有不同版本组件而不仅仅是版本字符串对命令行也没有影响。然而,如果许多不同的源文件都需要版本号,这仍然会导致比实际需要的次数更多的重新编译。通过将实现从标头移到它们自己的.cpp文件中并将其编译为自己的库,可以进一步完善此方法。
Providing version details through a header like this is an improvement over the
previous techniques. The version details are not included on the command line
of any source file’s compilation and only those files that #include the
foobar_version.h header will be recompiled when the version details change.
Providing all of the different version components rather than just the version
string also has no impact on command lines. Nevertheless, if the version number
is needed in many different source files, this can still result in more
recompilation than is really necessary. This approach can be further refined by
moving the implementations out of the header into their own .cpp file and
compiling that as its own library.
#include <string>
std::string getFooBarVersion();
unsigned getFooBarVersionMajor();
unsigned getFooBarVersionMinor();
unsigned getFooBarVersionPatch();
unsigned getFooBarVersionTweak();#include <string>
std::string getFooBarVersion();
unsigned getFooBarVersionMajor();
unsigned getFooBarVersionMinor();
unsigned getFooBarVersionPatch();
unsigned getFooBarVersionTweak();
#include "foobar_version.h"
std::string getFooBarVersion()
{
return "@FooBar_VERSION@";
}
unsigned getFooBarVersionMajor()
{
return @FooBar_VERSION_MAJOR@;
}
unsigned getFooBarVersionMinor()
{
return @FooBar_VERSION_MINOR@ +0;
}
unsigned getFooBarVersionPatch()
{
return @FooBar_VERSION_PATCH@ +0;
}
unsigned getFooBarVersionTweak()
{
return @FooBar_VERSION_TWEAK@ +0;
}#include "foobar_version.h"
std::string getFooBarVersion()
{
return "@FooBar_VERSION@";
}
unsigned getFooBarVersionMajor()
{
return @FooBar_VERSION_MAJOR@;
}
unsigned getFooBarVersionMinor()
{
return @FooBar_VERSION_MINOR@ +0;
}
unsigned getFooBarVersionPatch()
{
return @FooBar_VERSION_PATCH@ +0;
}
unsigned getFooBarVersionTweak()
{
return @FooBar_VERSION_TWEAK@ +0;
}
cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
configure_file(foobar_version.cpp.in foobar_version.cpp
@ONLY
)
add_library(FooBar_version STATIC
${CMAKE_CURRENT_BINARY_DIR}/foobar_version.cpp
)
target_include_directories(FooBar_version
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
)
add_executable(FooBar main.cpp)
target_link_libraries(FooBar PRIVATE FooBar_version)
add_library(FooToolkit mylib.cpp)
target_link_libraries(FooToolkit PRIVATE FooBar_version)cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
configure_file(foobar_version.cpp.in foobar_version.cpp
@ONLY
)
add_library(FooBar_version STATIC
${CMAKE_CURRENT_BINARY_DIR}/foobar_version.cpp
)
target_include_directories(FooBar_version
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
)
add_executable(FooBar main.cpp)
target_link_libraries(FooBar PRIVATE FooBar_version)
add_library(FooToolkit mylib.cpp)
target_link_libraries(FooToolkit PRIVATE FooBar_version)
这种布置没有先前方法的任何缺点。当版本详细信息发生变化时,只需重新编译一个源文件(生成的foobar_version.cpp文件),并且只需重新链接FooBar和目标文件。FooToolkit标foobar_version.h头永远不会更改,因此当版本详细信息发生更改时,依赖于它的任何文件都不会过时。任何源文件的编译命令行中也不会添加任何选项,因此更改版本详细信息不会触发其他重新编译。
This arrangement has none of the drawbacks of the previous approaches. When the
version details change, only one source file needs to be recompiled (the
generated foobar_version.cpp file) and the FooBar and FooToolkit targets
only need to be relinked. The foobar_version.h header never changes, so any
file that depends on it does not become out of date when the version details
change. No options are added to the compilation command line of any source file
either, so no other recompilations are triggered as a result of changing
version details.
在项目提供库和头文件作为发布包的一部分的情况下,上述安排也是稳健的。标头不包含版本详细信息,但库包含。因此,使用该库的代码可以调用版本函数,并确信它们收到的详细信息是构建该库所用的详细信息。这在复杂的最终用户环境中非常有用,在复杂的最终用户环境中可能会安装项目的多个版本,并且不一定按照项目的预期结构。
In situations where the project provides a library and header as part of a release package, the above arrangement is also robust. The header does not contain the version details, the library does. Therefore, code using the library can call the version functions and be confident that the details they receive are those the library was built with. This can be helpful in complicated end user environments where multiple versions of a project might be installed and not necessarily structured how the project intended.
这种方法的一个变体是创建FooBar_version一个对象库而不是静态库。最终结果或多或少是相同的,但没有太多收获,而且对于一些开发人员来说可能感觉不太自然。使其成为共享库会失去一些稳健性优势,并再次引入更多的复杂性,但几乎没有什么好处。一般来说,静态库是更好的选择。
One variant of this approach is to make FooBar_version an object library
rather than a static library.
The end result is more or less the same, but there isn’t much to be gained and
it may feel less natural to some developers.
Making it a shared library loses some of the robustness advantages and again
introduces a little more complexity for little benefit.
In general, a static library is the better choice.
如果版本函数要作为 API 的一部分公开以用于更广泛的共享库,则可能需要考虑第 21.5 节“符号可见性”
和第 21.6 节“混合静态库和共享库”中讨论的其他问题。在这种情况下,直接将文件添加到该共享库可能foobar_version.cpp比为其创建单独的静态库更合适。
If the version functions are to be exposed as part of the API for a broader
shared library, then the additional concerns discussed in Section 21.5, “Symbol Visibility”
and Section 21.6, “Mixing Static And Shared Libraries” may need to be considered.
In such cases, it may be more appropriate to add the foobar_version.cpp file
to that shared library directly rather than creating a separate static library
for it.
项目想要记录与其源代码控制系统相关的详细信息并不罕见。这可能包括构建时源的修订或提交哈希、当前分支的名称或最新标签等。上面概述的通过专用文件提供版本详细信息的方法.cpp非常适合添加更多函数来返回此类详细信息。例如,当前的 git hash 可以相对容易地提供:
It is not unusual for projects to want to record details related to their
source control system. This might include the revision or commit hash of the
sources at the time of the build, the name of the current branch or most recent
tag and so on. The approach outlined above with version details provided
through a dedicated .cpp file lends itself well to adding more functions to
return such details. For example, the current git hash can be provided
relatively easily:
std::string getFooBarGitHash()
{
return "@FooBar_GIT_HASH@";
}
// Other functions as before...std::string getFooBarGitHash()
{
return "@FooBar_GIT_HASH@";
}
// Other functions as before...
cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
# The find_package() command is covered later in the
# Finding Things chapter. Here, it provides the
# GIT_EXECUTABLE variable after searching for the git
# binary in some standard/well-known locations for the
# current platform.
find_package(Git REQUIRED)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
RESULT_VARIABLE result
OUTPUT_VARIABLE FooBar_GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(result)
message(FATAL_ERROR
"Failed to get git hash: ${result}"
)
endif()
configure_file(foobar_version.cpp.in foobar_version.cpp
@ONLY
)
# Targets, etc....cmake_minimum_required(VERSION 3.0)
project(FooBar VERSION 2.4.7)
# The find_package() command is covered later in the
# Finding Things chapter. Here, it provides the
# GIT_EXECUTABLE variable after searching for the git
# binary in some standard/well-known locations for the
# current platform.
find_package(Git REQUIRED)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
RESULT_VARIABLE result
OUTPUT_VARIABLE FooBar_GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(result)
message(FATAL_ERROR
"Failed to get git hash: ${result}"
)
endif()
configure_file(foobar_version.cpp.in foobar_version.cpp
@ONLY
)
# Targets, etc....
一个更有趣的例子是测量自特定文件更改以来发生了多少次提交。考虑将项目的版本嵌入到一个单独的文件中,而不是嵌入到文件中CMakeLists.txt,其中这个单独的文件中唯一的内容是项目版本号。然后可以做出合理的假设,即文件仅在版本号更改时更改。因此,测量自当前分支上的文件更改以来的提交次数通常可以很好地衡量自上次版本更新以来的提交次数。
A slightly more interesting example is measuring how many commits have occurred
since a particular file changed. Consider embedding the project’s version in a
separate file rather than in the CMakeLists.txt file, where the only thing in
this separate file is the project version number. A reasonable assumption can
then be made that the file only changes when the version number changes. As a
result, measuring the number of commits since that file changed on the current
branch is generally a good measure of the number of commits since the last
version update.
以下示例将项目版本移出到一个名为的单独文件
projectVersionDetails.cmake,并通过生成的文件中的新函数提供提交次数foobar_version.cpp。它演示了一种适合任何项目的模式,其中版本由顶级project()
调用设置,但如果将其合并到更大的项目层次结构中,则不会干扰父项目(
第 28.2 节中讨论的主题,“获取内容”)。
The following example moves the project version out to a separate file named
projectVersionDetails.cmake and provides the number of commits through a new
function in the generated foobar_version.cpp file. It demonstrates a pattern
suitable for any project where the version is set by the top level project()
call, but in a way that won’t interfere with a parent project if it is
incorporated into a larger project hierarchy (a topic discussed in
Section 28.2, “FetchContent”).
unsigned getFooBarCommitsSinceVersionChange()
{
return @FooBar_COMMITS_SINCE_VERSION_CHANGE@;
}
// Other functions as before...unsigned getFooBarCommitsSinceVersionChange()
{
return @FooBar_COMMITS_SINCE_VERSION_CHANGE@;
}
// Other functions as before...
# This file should contain nothing but the following line
# setting the project version. The variable name must not
# clash with the FooBar_VERSION* variables automatically
# defined by the project() command.
set(FooBar_VER 2.4.7)# This file should contain nothing but the following line
# setting the project version. The variable name must not
# clash with the FooBar_VERSION* variables automatically
# defined by the project() command.
set(FooBar_VER 2.4.7)
cmake_minimum_required(VERSION 3.0)
include(projectVersionDetails.cmake)
project(FooBar VERSION ${FooBar_VER})
find_package(Git REQUIRED)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-list
-1 HEAD projectVersionDetails.cmake
RESULT_VARIABLE result
OUTPUT_VARIABLE lastChangeHash
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(result)
message(FATAL_ERROR
"Failed to get hash of last change: ${result}"
)
endif()
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-list
${lastChangeHash}..HEAD
RESULT_VARIABLE result
OUTPUT_VARIABLE hashList
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(result)
message(FATAL_ERROR
"Failed to get list of git hashes: ${result}"
)
endif()
string(REGEX REPLACE "[\n\r]+" ";" hashList "${hashList}")
list(LENGTH hashList FooBar_COMMITS_SINCE_VERSION_CHANGE)
configure_file(foobar_version.cpp.in foobar_version.cpp
@ONLY
)
# Targets, etc....cmake_minimum_required(VERSION 3.0)
include(projectVersionDetails.cmake)
project(FooBar VERSION ${FooBar_VER})
find_package(Git REQUIRED)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-list
-1 HEAD projectVersionDetails.cmake
RESULT_VARIABLE result
OUTPUT_VARIABLE lastChangeHash
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(result)
message(FATAL_ERROR
"Failed to get hash of last change: ${result}"
)
endif()
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-list
${lastChangeHash}..HEAD
RESULT_VARIABLE result
OUTPUT_VARIABLE hashList
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(result)
message(FATAL_ERROR
"Failed to get list of git hashes: ${result}"
)
endif()
string(REGEX REPLACE "[\n\r]+" ";" hashList "${hashList}")
list(LENGTH hashList FooBar_COMMITS_SINCE_VERSION_CHANGE)
configure_file(foobar_version.cpp.in foobar_version.cpp
@ONLY
)
# Targets, etc....
上述方法计算出版本详细信息文件的最后一次更改的 git 哈希值,然后用于git rev-list获取自该提交以来整个存储库的提交哈希值列表。提交最初被发现为每行一个哈希值的字符串,然后通过用列表分隔符 ( ;) 替换换行符将其转换为 CMake 列表。然后,该list()
命令只需计算列表中有多少项即可给出提交数。更简单的方法是git rev-list --count直接获取数字,但旧版本的git不支持该--count选项,因此如果需要支持旧版本的git,则优选上述方法。
The above approach works out the git hash of the last change to the version
details file, then uses git rev-list to obtain the list of commit hashes for
the whole repository since that commit. The commits are initially found as a
string with one hash per line, which is then converted into a CMake list by
replacing newline characters with the list separator (;). The list()
command then simply counts how many items are in the list to give the number of
commits. A simpler approach would use git rev-list --count to obtain the
number directly, but older versions of git do not support the --count option,
so the above method is preferable if older git versions need to be supported.
其他变化也是可能的。某些项目用于git describe提供各种详细信息,包括分支名称、最新标签等,但请注意,标签和分支详细信息可以在不更改提交的情况下更改。如果分支或标签被移动或重命名,则构建可能无法重复。如果版本详细信息仅依赖于文件提交哈希,则不会创建此类弱点。这也使得项目在构建确认提交没有错误后可以根据需要自由地创建、重命名或删除标签(想象一下在持续集成构建、测试等确认没有问题之后将发布标签应用于提交)。
Other variations are also possible. Some projects use git describe to provide
various details including branch names, most recent tag, etc., but note that
tag and branch details can change without changing commits. If a branch or tag
is moved or renamed, the build might not be repeatable. If version details only
rely on file commit hashes, no such weakness is created. This also gives the
project freedom in creating, renaming or deleting tags as needed after builds
have confirmed the commits have no errors (think of release tags being applied
to commits after continuous integration builds, testing, etc. confirm there are
no problems).
像 Subversion 这样的源代码控制系统还带来了其他挑战。一方面,Subversion 维护了整个存储库的全局修订号,因此无需先获取提交哈希值,然后再对其进行计数。但 Subversion 也有一个复杂之处,即它允许混合不同文件的不同版本。因此,开发人员检查文件的不同修订版本但保留项目版本文件时,可能会击败上面概述的 git 方法。这不是人们期望的自动化持续集成系统的场景,但对于在自己的机器上本地工作的开发人员来说,这种情况更有可能发生,具体取决于他们喜欢的工作方式。
Source control systems like Subversion present other challenges. On the one hand, Subversion maintains a global revision number for the whole repository, so there is no need to first obtain commit hashes and then count them. But Subversion also has the complication that it allows mixing different revisions of different files. As a result, approaches like the one outlined above for git can be defeated by a developer checking out different revisions of files but leaving the project version file alone. This is not a scenario one would expect for an automated continuous integration system, but it may be more likely for a developer working locally on their own machine, depending on the way they like to work.
上述技术的另一个考虑因素是强制.cpp更新生成的版本文件。CMake 确保在项目版本文件发生更改时重新运行配置步骤,因为它是CMakeLists.txt通过include()命令带入主文件的。但是,如果对其他文件进行提交,CMake 将不会意识到它们。也许可以在版本控制系统中实现挂钩(例如 git 的提交后挂钩)来强制 CMake 重新运行,但这更有可能惹恼开发人员,而不是帮助他们。最终,通常会在便利性和稳健性之间做出折衷。也就是说,源代码控制细节的准确性可能只对发布至关重要,并且应该很容易确保发布过程显式调用 CMake。
Another consideration of techniques like those above is what forces the
generated version .cpp file to be updated. CMake ensures the configure step
is re-run if the project version file changes, since it is brought into the
main CMakeLists.txt file via an include() command. If, however, commits are
made to other files, CMake will not be aware of them. It may be possible to
implement hooks into the version control system (e.g. git’s post-commit hook)
to force CMake to re-run, but this is more likely to annoy developers than to
help them. Ultimately, a compromise between convenience and robustness will
typically be made. That said, the accuracy of the source control details will
likely only be critical for releases and it should be easy enough to ensure
that the release process explicitly invokes CMake.
项目不需要遵循任何特定的版本控制系统,但通过遵循major.minor.patch.tweak格式,CMake 可以免费提供某些功能,并且新开发人员可以更轻松地理解项目使用的版本控制。正如在后面的章节(特别是第 27 章,打包)中将看到的 ,在制作打包版本时,版本格式更为重要,但由于许多项目在运行时报告自己的版本号,因此版本格式也会影响构建。
Projects are not required to follow any particular versioning system, but by following the major.minor.patch.tweak format, certain functionality comes for free with CMake and new developers have an easier time understanding the versioning used by the project. As will be seen in later chapters (notably Chapter 27, Packaging), the version format is more important when making packaged releases, but since many projects report their own version number at run time, the version format affects the build as well.
组成版本格式的每个数字的含义取决于项目,但最终用户通常期望一些约定。例如,主要值的更改通常意味着重大发布,通常涉及不向后兼容的更改或代表项目方向的更改。如果较小的值发生变化,用户往往会将其视为增量版本,很可能会在不破坏现有行为的情况下添加新功能。当仅补丁值发生变化时,用户可能不会将其视为特别重要的更改,并期望其相对较小,例如修复一些错误但不引入新功能。调整值经常被省略,并且除了比patch更不重要之外,往往没有通用的解释。请注意,这些只是一般观察,项目可以并且确实赋予版本号完全不同的含义。为了最终的简单性,项目可能只使用一个数字而不使用其他任何东西,从而有效地将每个版本指定为新的主要 版本。虽然这很容易实现,但它为最终用户提供的指导也较少,并且需要高质量的发行说明来管理每个版本之间的用户期望。
The meaning of each of the numbers making up the version format is up to the project, but there are conventions that end users often expect. For example, a change in the major value usually means a significant release, often involving changes that are not backward compatible or that represent a change in direction for the project. If a minor value changes, users tend to see this as an incremental release, most likely adding new features without breaking existing behavior. When only the patch value changes, users may not see it as a particularly important change and expect it to be relatively minor, such as fixing some bugs but not introducing new functionality. The tweak value is often omitted and doesn’t tend to have a common interpretation beyond being even less significant than patch. Note that these are just general observations, projects can and do give the version numbers completely different meanings. For ultimate simplicity, a project might use just a single number and nothing else, effectively specifying every release as a new major version. While this would be easy to implement, it would also provide less guidance to end users and require good quality release notes to manage user expectations between each version.
该命令VERSION的关键字是 CMake 如何在使用Major.minor.patch.tweakproject()格式时提供额外便利的示例之一。该项目提供单个版本字符串,并且该命令自动定义一组变量,使版本号的各个部分可用。某些 CMake 模块也可能使用这些变量作为某些元数据的默认值,因此通常建议使用关键字的命令设置项目版本。这个关键字是在CMake 3.0中添加的,但是如果支持旧的CMake版本,仍然需要考虑这个功能。项目不应定义名称与自动定义的名称冲突的变量,否则更高版本的 CMake 会发出警告。避免使用表单名称显式设置变量或防止出现此类警告。project()project()VERSIONxxx_VERSIONxxx_VERSION_yyy
The VERSION keyword of the project() command is one example of how CMake
provides extra convenience when the major.minor.patch.tweak format is used.
The project provides a single version string and the project() command
automatically defines a set of variables making the various parts of the
version number available. Some CMake modules may also use these variables as
defaults for certain meta data, so it is generally advisable to set the project
version with the project() command using the VERSION keyword. This keyword
was added in CMake 3.0, but if supporting older CMake versions, this
functionality still needs to be considered. Projects should not define
variables whose names clash with the automatically defined ones or else later
CMake versions will issue a warning. Avoid explicitly setting variables with
names of the form xxx_VERSION or xxx_VERSION_yyy to prevent such warnings.
定义版本号时,请考虑在其自己的专用文件中执行此操作,然后 CMake 通过include()命令拉入该文件。这允许项目利用版本号的更改与项目的源代码控制系统所看到的文件中的更改保持一致。为了最大限度地减少版本更改时不必要的重新编译,请生成一个.c或.cpp文件,其中包含返回版本详细信息的函数,而不是将这些详细信息嵌入到生成的标头中或作为要在命令行上传递的编译器定义。还要确保为此类函数指定的名称包含特定于项目的内容或将它们放置在特定于项目的命名空间中。这允许在许多项目中复制相同的模式,这些项目稍后可以合并到单个构建中,而不会导致名称冲突。
When defining the version number, consider doing so in its own dedicated file
which CMake then pulls in via an include() command. This allows the project
to take advantage of changes in version number aligning with changes in that
file as seen by the project’s source control system. To minimize unnecessary
recompilation on version changes, generate a .c or .cpp file which contains
functions that return version details rather than embedding those details in a
generated header or as compiler definitions to be passed on the command line.
Also ensure that names given to such functions incorporate something specific
to the project or place them in a project-specific namespace. This allows the
same pattern to be replicated across many projects which may later be combined
into a single build without causing name clashes.
在项目生命周期的早期建立版本控制策略和实施模式。这有助于开发人员清楚地了解版本详细信息如何以及何时更新,并鼓励在第一次交付的压力之前就考虑发布过程。它还允许尽早淘汰效率较低的方法,以便在版本号发生变化且构建周转时间可能变得更加重要的版本发布之前最大化构建效率。
Establish versioning strategies and implementation patterns early in a project’s life. This helps developers gain a clear understanding about how and when version details get updated and it encourages thinking about the release process well before the pressures of the first delivery. It also allows less efficient approaches to be weeded out early so that build efficiency is maximized in advance of releases where version numbers change and where build turnaround times may become more important.
与编写普通应用程序相比,创建和维护库通常更复杂,尤其是共享库。所有对代码正确性和可维护性的常见担忧仍然适用,但特别是共享库还带来了与 API 一致性、保持版本之间的二进制兼容性、符号可见性等相关的额外考虑因素。此外,每个平台通常都有自己的一套独特的功能和要求,使得跨平台库开发成为一项具有挑战性的任务。
Compared to writing ordinary applications, creating and maintaining libraries is typically more involved, especially shared libraries. All the usual concerns about code correctness and maintainability still apply, but shared libraries in particular also bring with them additional considerations relating to API consistency, preserving binary compatibility between releases, symbol visibility and more. Furthermore, each platform typically has its own set of unique features and requirements, making cross-platform library development a challenging task.
然而,在大多数情况下,所有主要平台都支持一组核心功能,只是定义或使用它们的方式有所不同。CMake 提供了许多功能来抽象这些差异,以便开发人员可以专注于功能,并将实现细节留给构建系统。
For the most part, however, a core set of capabilities are supported by all major platforms, it’s just that the way to define or use them varies. CMake provides a number of features which abstract away these differences so that developers can focus on the capabilities and leave the implementation details up to the build system.
前面的章节介绍了定义库的基本命令,其形式如下:
The fundamental command for defining a library was covered in previous chapters and has the following form:
add_library(targetName [STATIC | SHARED | MODULE | OBJECT]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])add_library(targetName [STATIC | SHARED | MODULE | OBJECT]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
如果提供了SHAREDor关键字,将会生成一个共享库。MODULE或者,如果没有给出STATIC、SHARED、MODULE或关键字,则如果调用时变量的值为 true,OBJECT则将生成共享库。BUILD_SHARED_LIBSadd_library()
A shared library will be produced if either the SHARED or MODULE keyword is
provided. Alternatively, if no STATIC, SHARED, MODULE or OBJECT keyword
is given, a shared library will be produced if the BUILD_SHARED_LIBS variable
has a value of true at the time add_library() is called.
SHARED和之间的主要区别MODULE在于,SHARED库旨在供其他目标链接,而MODULE库则不然。MODULE库通常用于插件或其他可以在运行时加载的可选库之类的东西。此类库的加载通常取决于应用程序配置设置或某些系统功能的检测。其他可执行文件和库通常不会链接到
MODULE库。
The main difference between SHARED and MODULE is that SHARED libraries
are intended for other targets to link against, whereas MODULE libraries are
not. MODULE libraries are typically used for things like plugins or other
optional libraries that can be loaded at runtime. The loading of such libraries
is often dependent on an application configuration setting or detection of some
system feature. Other executables and libraries do not normally link against a
MODULE library.
在大多数基于 Unix 的平台上,默认情况下会在前面添加 aSTATIC或库的文件名,但也可能不会。Apple 平台还支持框架和可加载捆绑包,允许将其他文件与库捆绑在定义良好的目录结构中。第 23.3 节“框架”对此进行了详细介绍。SHAREDlibMODULE
On most Unix-based platforms, the file name of a STATIC or SHARED library
will have lib prepended by default, whereas MODULE might not. Apple
platforms also support frameworks and loadable bundles, which allow additional
files to be bundled with the library in a well-defined directory structure.
This is covered in detail in Section 23.3, “Frameworks”.
lib在 Windows 平台上,无论库的类型如何,库名称都没有任何前缀。静态库目标生成单个
.lib存档,而共享库目标生成两个单独的文件,一个用于运行时(.dll或动态链接库),另一个用于在构建时链接(即.lib导入库)。开发人员有时会混淆导入库和静态库,因为两者使用相同的文件后缀,但 CMake 通常无需任何特殊干预即可正确处理它们。
On Windows platforms, library names do not have any lib prefix prepended,
regardless of the type of library. Static library targets produce a single
.lib archive, whereas shared library targets result in two separate files,
one for the runtime (the .dll or dynamic link library) and the other for
linking against at build time (i.e. the .lib import library). Developers
sometimes confuse import and static libraries due to the same file suffix being
used for both, but CMake generally handles them correctly without any special
intervention.
在 Windows 上使用 GNU 工具时(例如,使用 MinGW 或 MSYS 项目生成器),CMake 能够将 GNU 导入库 ( .dll.a) 转换为 Visual Studio 生成的相同格式 ( ) .lib)。如果分发使用 GNU 构建的共享库,这可能会很有用工具使其能够链接到使用 Visual Studio 构建的二进制文件。请注意,必须安装 Visual Studio 才能进行此转换。通过将
GNUtoMS共享库的目标属性设置为 true 来启用转换。此目标属性由以下方式初始化调用CMAKE_GNUtoMS当时变量的值。add_library()
When using GNU tools on Windows (e.g. with the MinGW or MSYS project
generators), CMake has the ability to convert GNU import libraries (.dll.a)
to the same format that Visual Studio produces (.lib). This can be useful if
distributing a shared library built with GNU tools to enable it to be linked to
binaries built with Visual Studio. Note that Visual Studio must be installed
for this conversion to be possible. The conversion is enabled by setting the
GNUtoMS target property to true for a shared library. This target
property is initialized by the value of the CMAKE_GNUtoMS variable at the
time add_library() is called.
CMake 处理一些特定于链接静态库的特殊情况。如果一个库A被列为PRIVATE静态库目标的依赖项
B,那么就链接而言(并且仅A用于链接) ,它将被有效地视为依赖项。这是因为仍然需要将私有
库添加到任何链接到的链接器命令行中,以便在链接时找到符号。如果
是共享库,则它所依赖的私有库不需要在链接器命令行上列出。这一切都由 CMake 透明地处理,因此开发人员通常不需要关心除了指定和的依赖关系之外的细节。PUBLICABABAPUBLICPRIVATEINTERFACEtarget_link_libraries()
CMake handles some special cases specific to linking static libraries. If a
library A is listed as a PRIVATE dependency for a static library target
B, then A will effectively be treated as a PUBLIC dependency as far as
linking is concerned (and only for linking). This is because the private A
library will still need to be added to the linker command line of anything
linking to B in order for symbols from A to be found at link time. If B
was a shared library, the private library A that it depends on would not need
to be listed on the linker command line. This is all handled transparently by
CMake, so the developer typically doesn’t need to concern themselves with the
details beyond specifying the PUBLIC, PRIVATE and INTERFACE dependencies
with target_link_libraries().
在典型的项目中,静态库不会包含两个或多个库相互依赖的循环依赖关系。然而,某些场景会导致这种情况,只要指定了相关链接关系(即通过target_link_libraries()),CMake 就会识别并处理循环依赖。CMake 文档中的示例的稍微修改版本突出显示了该行为:
In typical projects, static libraries will not contain cyclic dependencies
where two or more libraries depend on each other. Nevertheless, some scenarios
give rise to such situations and CMake will recognize and handle the cyclic
dependency as long as the relevant linking relationships have been specified
(i.e. by target_link_libraries()). A slightly modified version of the example
from the CMake documentation highlights the behavior:
add_library(A STATIC a.cpp)
add_library(B STATIC b.cpp)
target_link_libraries(A PUBLIC B)
target_link_libraries(B PUBLIC A)
add_executable(Main main.cpp)
target_link_libraries(Main A)add_library(A STATIC a.cpp)
add_library(B STATIC b.cpp)
target_link_libraries(A PUBLIC B)
target_link_libraries(B PUBLIC A)
add_executable(Main main.cpp)
target_link_libraries(Main A)
在上面,链接命令Main将包含A B A B. 这种重复是由 CMake 自动提供的,无需开发人员干预,但在某些病态情况下,可能需要多次重复。虽然 CMakeLINK_INTERFACE_MULTIPLICITY为此目的提供了目标属性,但这种情况通常表明需要重组项目。OBJECT图书馆也可能是解决这种深层相互依赖性的有用工具,因为它们实际上就像资源的集合而不是实际的图书馆。链接器命令行上目标文件的顺序通常并不重要,而库顺序通常很重要。
In the above, the link command for Main will contain A B A B. This
repetition is provided automatically by CMake without developer intervention,
but in certain pathological cases, more than one repetition may be required.
While CMake provides the LINK_INTERFACE_MULTIPLICITY target property for
this purpose, such situations usually point to a need for the project to be
restructured. OBJECT libraries may also be a useful tool for addressing such
deep interdependencies, since they effectively act like a collection of sources
rather than actual libraries. The ordering of object files on the linker
command line is usually not important, whereas library ordering typically is.
不希望其库在项目本身之外使用的 CMake 项目通常不需要其创建的任何共享库的版本信息。整个项目往往在部署时一起更新,因此确保版本之间的二进制兼容性等问题很少。但如果项目提供库并且其他软件可以链接到它们,则库版本控制变得非常重要。库版本详细信息增加了更大的稳健性,允许其他软件指定它们期望链接并在运行时可用的接口。
A CMake project which does not expect its libraries to be used outside of the project itself doesn’t typically need version information for any shared libraries it creates. The whole project tends to be updated together when deployed, so there are few issues about ensuring binary compatibility between releases, etc. But if the project provides libraries and other software could link against them, library versioning becomes very important. Library version details add greater robustness, allowing other software to specify the interface they expect to link against and have available to them at run time.
大多数平台都提供指定共享库版本号的功能,但其完成方式差异很大。平台通常能够将版本详细信息编码到共享库二进制文件中,并且此信息有时用于确定二进制文件是否可以由链接到它的另一个可执行文件或共享库使用。某些平台还具有设置文件和符号链接的约定,其名称中包含不同级别的版本号。例如,在 Linux 上,共享库的一组通用文件和符号链接可能如下所示:
Most platforms offer functionality for specifying the version number of a shared library, but the way it is done varies considerably. Platforms generally have the ability to encode version details into the shared library binary and this information is sometimes used to determine whether a binary can be used by another executable or shared library that links to it. Some platforms also have conventions for setting up files and symbolic links with different levels of the version number in their names. On Linux, for example, a common set of file and symbolic links for a shared library might look like this:
libMyStuff.so.2.4.3 libMyStuff.so.2 --> libMyStuff.so.2.4.3 libMyStuff.so --> libMyStuff.so.2
libMyStuff.so.2.4.3 libMyStuff.so.2 --> libMyStuff.so.2.4.3 libMyStuff.so --> libMyStuff.so.2
CMake 负责处理共享库版本处理方面的大部分平台差异。将目标链接到共享库时,它将遵循平台约定来决定链接哪个文件或符号链接名称。构建共享库时,如果提供了版本详细信息,CMake 会自动创建全套文件和符号链接。
CMake takes care of most of the platform differences with regard to version handling for shared libraries. When linking a target to a shared library, it will follow platform conventions when deciding which of the file or symlink names to link against. When building a shared library, CMake automates the creation of the full set of files and symlinks if version details are provided.
VERSION共享库的版本详细信息由和
target 属性
定义SOVERSION。这些属性的解释在 CMake 支持的平台上有所不同,但通过遵循语义版本控制原则,可以相当无缝地处理这些差异。语义版本控制假设版本号以major.minor.patch 的形式指定
,其中每个版本组件都是一个整数。该VERSION
属性将设置为完整的Major.minor.patch,而SOVERSION
仅设置为主要部分。随着项目的发展和发布,语义版本控制意味着版本详细信息应修改如下:
A shared library’s version details are defined by the VERSION and
SOVERSION target properties. The interpretation of these properties is
different across the platforms CMake supports, but by following semantic
versioning principles, these differences can be handled fairly seamlessly.
Semantic versioning assumes a version number is specified in the form
major.minor.patch, where each version component is an integer. The VERSION
property would be set to the full major.minor.patch, whereas SOVERSION
would be set to just the major part. As a project evolves and makes releases,
semantic versioning implies that the version details should be modified as
follows:
SOVERSION每次出现 API 损坏时,并且
仅当出现 API 损坏时,属性才会更改。
SOVERSION property will change every time there is an API breakage and
only if there is an API breakage.
如果根据这些原则修改共享库的版本详细信息,则所有平台上运行时的 API 不兼容问题都将最小化。考虑以下示例,它生成前面所示的 Linux 符号链接集:
If the version details of a shared library are modified according to these principles, API incompatibility issues at run time will be minimized on all platforms. Consider the following example, which produces the set of symbolic links shown earlier for Linux:
add_library(MyStuff SHARED source1.cpp ...)
set_target_properties(MyStuff PROPERTIES
VERSION 2.4.3
SOVERSION 2
)add_library(MyStuff SHARED source1.cpp ...)
set_target_properties(MyStuff PROPERTIES
VERSION 2.4.3
SOVERSION 2
)
在 Apple 平台上,该otool -L命令可用于打印编码到生成的共享库中的版本详细信息。上述示例生成的共享库的输出将报告版本详细信息,兼容性版本为 2.0.0,当前版本为 2.4.3。任何与MyStuff库链接的内容都会将名称
libMyStuff.2.dylib编码为运行时要查找的库的名称。Linux 平台在共享库的符号链接中显示了类似的结构,通常的做法是仅使用库 soname 的主要部分。
On Apple platforms, the otool -L command can be used to print the version
details encoded into the resultant shared library. The output for the shared
library produced by the above example would report the version details as
having a compatibility version of 2.0.0 and current version 2.4.3. Anything
that linked against the MyStuff library would have the name
libMyStuff.2.dylib encoded into it as the name of the library to look for at
run time. Linux platforms show a similar structure in their symbolic links for
shared libraries and normal practice is to use just the major part for the
library’s soname.
CMake 3.17 添加了MACHO_COMPATIBILITY_VERSION和
MACHO_CURRENT_VERSIONtarget 属性以支持 Apple 平台的高级用例(通常与匹配libtool约定相关)。这些附加属性允许文件和符号链接命名与 Mach-O 二进制文件中嵌入的内部名称分离。项目很少需要这种更复杂的功能,并且建议避免使用这些属性,除非特定场景需要它们。
CMake 3.17 added the MACHO_COMPATIBILITY_VERSION and
MACHO_CURRENT_VERSION target properties to support advanced use cases for
Apple platforms (typically related to matching libtool conventions).
These additional properties allow file and symlink naming to be decoupled from
the internal names embedded in the Mach-O binaries.
Projects should rarely need this more complex functionality and are advised to
avoid using these properties unless specific scenarios require them.
在 Windows 上,CMake 的行为是从
属性中提取主要VERSION版本和次要版本,并将其编码到 DLL 中作为 DLL 映像版本。Windows 没有 soname 的概念,因此SOVERSION不使用该属性。尽管如此,遵循语义版本控制原则至少可以确保 DLL 版本可用于确定库与链接到它的二进制文件的兼容性。
On Windows, CMake behavior is to extract a major.minor version from the
VERSION property and encode that into the DLL as the DLL image version.
Windows does not have the concept of a soname, so the SOVERSION property is
not used. Nevertheless, following semantic versioning principles will at least
ensure that the DLL version can be used to determine the compatibility of the
library with binaries that link against it.
应该注意的是,任何平台都没有严格要求语义版本控制。相反,它提供了一个定义良好的规范,为共享库和使用它们的事物之间的依赖关系管理带来了一定的确定性。它恰好密切反映了大多数基于 Unix 的平台上通常如何解释库版本,并且 CMake 的目标是充分利用VERSION和SOVERSION目标属性来提供遵循本机平台约定的共享库。
It should be noted that semantic versioning is not strictly required by any
platform. Rather, it provides a well defined specification which brings some
certainty around dependency management between shared libraries and the things
that use them. It happens to closely reflect how library versions are usually
interpreted on most Unix-based platforms and CMake aims to make the most of
the VERSION and SOVERSION target properties to provide shared libraries
which follow native platform conventions.
VERSION项目应该意识到,如果仅设置了和target 属性之一SOVERSION
,则在大多数平台上,缺少的属性将被视为与提供的属性具有相同的值。这不太可能导致良好的版本处理,除非仅使用单个数字作为版本号(即没有次要部分或补丁部分)。这种版本编号在某些情况下可能是合适的,但项目通常应努力遵循上面讨论的原则,以获得更灵活和更健壮的运行时行为。
Projects should be aware that if only one of the VERSION and SOVERSION
target properties are set, on most platforms the missing one is treated as
though it had the same value as the one that was provided.
This is unlikely to result in good version handling unless just a single number
is used for the version number (i.e. no minor or patch parts).
Such version numbering may be appropriate in certain cases, but projects should
generally endeavor to follow the principles discussed above for more flexible
and more robust runtime behavior.
和target 属性允许以独立于平台VERSION的SOVERSION方式在二进制级别指定 API 版本控制。CMake 还提供其他属性,可用于定义 CMake 目标相互链接时之间的兼容性要求。这些可用于描述和强制执行仅版本编号无法捕获的细节。
The VERSION and SOVERSION target properties allow API versioning to be
specified at the binary level in a platform-independent manner.
CMake also provides other properties which can be used to define requirements
for compatibility between CMake targets when they are linked to one another.
These can be used to describe and enforce details that version numbering alone
cannot capture.
考虑一个现实的例子,其中网络库仅https://在适当的 SSL 工具包可用的情况下提供对协议和其他类似安全功能的支持。程序的其他部分可能需要根据是否支持 SSL 来调整自己的功能,而程序作为一个整体应该在是否可以使用 SSL 功能方面保持一致。这可以通过接口兼容性属性来强制执行。
Consider a realistic example where a networking library only provides support
for the https:// protocol and other similar secure capabilities if an
appropriate SSL toolkit is available. Other parts of the program may need to
adjust their own functionality based on whether or not SSL is supported, while
the program as a whole should be consistent about whether or not SSL features
can be used. This can be enforced with an interface compatibility property.
可以定义几种不同类型的接口兼容性属性,但最简单的是布尔属性。基本思想是,库指定将用于宣传特定布尔状态的属性名称,然后使用相关值定义该属性。当链接在一起的多个库为接口兼容性定义相同的属性名称时,CMake 将检查它们是否指定相同的值,如果不同,则发出错误。一个基本的例子看起来像这样:
A few different types of interface compatibility properties can be defined, but the simplest is a boolean property. The basic idea is that libraries specify the name of a property they will use to advertise a particular boolean state and then they define that property with the relevant value. When multiple libraries that are being linked together define the same property name for an interface compatibility, CMake will check that they specify the same value and issue an error if they are different. A basic example looks something like this:
add_library(Networking net.cpp)
set_target_properties(Networking PROPERTIES
COMPATIBLE_INTERFACE_BOOL SSL_SUPPORT
INTERFACE_SSL_SUPPORT YES
)
add_library(Util util.cpp)
set_target_properties(Util PROPERTIES
COMPATIBLE_INTERFACE_BOOL SSL_SUPPORT
INTERFACE_SSL_SUPPORT YES
)
add_executable(MyApp myapp.cpp)
target_link_libraries(MyApp PRIVATE Networking Util)
target_compile_definitions(MyApp PRIVATE
$<$<BOOL:$<TARGET_PROPERTY:SSL_SUPPORT>>:HAVE_SSL>
)add_library(Networking net.cpp)
set_target_properties(Networking PROPERTIES
COMPATIBLE_INTERFACE_BOOL SSL_SUPPORT
INTERFACE_SSL_SUPPORT YES
)
add_library(Util util.cpp)
set_target_properties(Util PROPERTIES
COMPATIBLE_INTERFACE_BOOL SSL_SUPPORT
INTERFACE_SSL_SUPPORT YES
)
add_executable(MyApp myapp.cpp)
target_link_libraries(MyApp PRIVATE Networking Util)
target_compile_definitions(MyApp PRIVATE
$<$<BOOL:$<TARGET_PROPERTY:SSL_SUPPORT>>:HAVE_SSL>
)
两个库目标都宣称它们为属性名称定义了接口兼容性SSL_SUPPORT。该COMPATIBLE_INTERFACE_BOOL属性应包含一个名称列表,每个名称都需要INTERFACE_在该目标上定义一个具有相同名称的关联属性。当这些库一起用作 的链接依赖项时MyApp,CMake 会检查两个库是否定义了INTERFACE_SSL_SUPPORT相同的值。此外,CMake 还将自动使用相同的值填充目标SSL_SUPPORT的属性MyApp,然后可以将其用作生成器表达式的一部分,并可作为编译定义的源代码使用,MyApp如图所示。这允许MyApp代码根据 SSL 支持是否已编译到它使用的库中进行定制。继续该示例,它不是MyApp简单地检测 SSL 支持是否可用,而是可以通过显式定义其SSL_SUPPORT属性来指定要求,以保存库必须兼容的值。在这种情况下,
CMake 将比较这些值并确保库与指定的要求一致,而不是自动填充SSL_SUPPORT的属性。MyApp
Both library targets advertise that they define an interface compatibility for
the property name SSL_SUPPORT. The COMPATIBLE_INTERFACE_BOOL property is
expected to hold a list of names, each of which requires an associated property
of the same name with INTERFACE_ prepended to be defined on that target.
When the libraries are used together as a link dependency for MyApp, CMake
checks that both libraries define INTERFACE_SSL_SUPPORT with the same value.
In addition, CMake will also automatically populate the SSL_SUPPORT property
of the MyApp target with the same value too, which can then be used as part
of a generator expression and made available to the source code of MyApp as a
compile definition as shown. This allows the MyApp code to tailor itself to
whether or not SSL support has been compiled into the libraries it uses.
Continuing with the example, rather than MyApp simply detecting whether
or not SSL support is available, it can specify a requirement by explicitly
defining its SSL_SUPPORT property to hold the value that the libraries must
be compatible with. In that case, rather than automatically populating the
SSL_SUPPORT property of MyApp, CMake will compare the values and
ensure the libraries are consistent with the specified requirement.
# Require libraries to have SSL support
set_target_properties(MyApp PROPERTIES SSL_SUPPORT YES)# Require libraries to have SSL support
set_target_properties(MyApp PROPERTIES SSL_SUPPORT YES)
上面的示例有些人为,可以通过其他方式强制执行相同的约束。当项目变得更加复杂并且其目标分布在许多目录或来自外部构建的项目时,接口兼容性规范的真正优势开始显现。接口兼容性被指定为目标的属性,因此只需在一处定义它们,然后就可以在任何可以使用目标的地方使用它们,而无需进一步努力。使用目标不必知道如何确定接口兼容性的细节,只需知道存储在目标
INTERFACE_…属性中的最终决定即可。
The above examples are somewhat contrived, the same constraints could have been
enforced in other ways.
The real advantages of interface compatibility specifications start to emerge
as a project becomes more complicated and its targets are spread across many
directories or come from externally built projects.
Interface compatibilities are assigned as properties of the targets, so they
only need to be defined in one place and are then made available anywhere the
target can be used without further effort.
Consuming targets don’t have to know the details of how the interface
compatibility is determined, only the final decision stored in the target’s
INTERFACE_… properties.
CMake 还支持以字符串表示的接口兼容性。它们的工作方式本质上与布尔情况相同,只是命名属性需要具有完全相同的值并且可以包含任意内容。可以修改前面的示例以要求库使用相同的 SSL 实现,而不仅仅是就它们是否支持 SSL 达成一致:
CMake also supports interface compatibilities expressed as a string. These work essentially the same way as the boolean case except that the named properties are required to have exactly the same values and can hold any arbitrary contents. The earlier example can be modified to require that libraries use the same SSL implementation, not just agree on whether they support SSL or not:
add_library(Networking net.cpp)
set_target_properties(Networking PROPERTIES
COMPATIBLE_INTERFACE_STRING SSL_IMPL
INTERFACE_SSL_IMPL OpenSSL
)
add_library(Util util.cpp)
set_target_properties(Util PROPERTIES
COMPATIBLE_INTERFACE_STRING SSL_IMPL
INTERFACE_SSL_IMPL OpenSSL
)
add_executable(MyApp myapp.cpp)
target_link_libraries(MyApp PRIVATE Networking Util)
target_compile_definitions(MyApp PRIVATE
SSL_IMPL=$<TARGET_PROPERTY:SSL_IMPL>
)add_library(Networking net.cpp)
set_target_properties(Networking PROPERTIES
COMPATIBLE_INTERFACE_STRING SSL_IMPL
INTERFACE_SSL_IMPL OpenSSL
)
add_library(Util util.cpp)
set_target_properties(Util PROPERTIES
COMPATIBLE_INTERFACE_STRING SSL_IMPL
INTERFACE_SSL_IMPL OpenSSL
)
add_executable(MyApp myapp.cpp)
target_link_libraries(MyApp PRIVATE Networking Util)
target_compile_definitions(MyApp PRIVATE
SSL_IMPL=$<TARGET_PROPERTY:SSL_IMPL>
)
在上面,该SSL_IMPL属性用作与指定它们用作OpenSSLSSL 实现的库的字符串接口兼容性。就像布尔值的情况一样,MyApp目标可以定义其SSL_IMPL属性来指定需求,而不是让 CMake 使用库中的值填充它。
In the above, the SSL_IMPL property is used as a string interface
compatibility with the libraries specifying that they use OpenSSL as their
SSL implementation. Just as for the boolean case, the MyApp target could have
defined its SSL_IMPL property to specify a requirement rather than letting
CMake populate it with the value from the libraries.
CMake 支持的另一种接口兼容性是数值。数字接口兼容性用于确定为一组库中的属性定义的最小值或 最大值,而不是要求属性具有相同的值。可以利用这一点来允许目标检测诸如它可以支持的最小协议版本之类的东西,或者计算出它链接到的库中所需的最大临时缓冲区大小。
The other kind of interface compatibility CMake supports is a numeric value. Numeric interface compatibilities are used to determine the minimum or maximum value defined for a property among a set of libraries rather than to require the properties to have the same value. This can be exploited to allow a target to detect things like a minimum protocol version it could support or to work out the largest temporary buffer size needed among the libraries it links to.
add_library(BigFast strategy1.cpp)
set_target_properties(BigFast PROPERTIES
COMPATIBLE_INTERFACE_NUMBER_MIN PROTOCOL_VER
COMPATIBLE_INTERFACE_NUMBER_MAX TMP_BUFFERS
INTERFACE_PROTOCOL_VER 3
INTERFACE_TMP_BUFFERS 200
)
add_library(SmallSlow strategy2.cpp)
set_target_properties(SmallSlow PROPERTIES
COMPATIBLE_INTERFACE_NUMBER_MIN PROTOCOL_VER
COMPATIBLE_INTERFACE_NUMBER_MAX TMP_BUFFERS
INTERFACE_PROTOCOL_VER 2
INTERFACE_TMP_BUFFERS 15
)
add_executable(MyApp myapp.cpp)
target_link_libraries(MyApp PRIVATE BigFast SmallSlow)
target_compile_definitions(MyApp PRIVATE
MIN_API=$<TARGET_PROPERTY:PROTOCOL_VER>
TMP_BUFFERS=$<TARGET_PROPERTY:TMP_BUFFERS>
)add_library(BigFast strategy1.cpp)
set_target_properties(BigFast PROPERTIES
COMPATIBLE_INTERFACE_NUMBER_MIN PROTOCOL_VER
COMPATIBLE_INTERFACE_NUMBER_MAX TMP_BUFFERS
INTERFACE_PROTOCOL_VER 3
INTERFACE_TMP_BUFFERS 200
)
add_library(SmallSlow strategy2.cpp)
set_target_properties(SmallSlow PROPERTIES
COMPATIBLE_INTERFACE_NUMBER_MIN PROTOCOL_VER
COMPATIBLE_INTERFACE_NUMBER_MAX TMP_BUFFERS
INTERFACE_PROTOCOL_VER 2
INTERFACE_TMP_BUFFERS 15
)
add_executable(MyApp myapp.cpp)
target_link_libraries(MyApp PRIVATE BigFast SmallSlow)
target_compile_definitions(MyApp PRIVATE
MIN_API=$<TARGET_PROPERTY:PROTOCOL_VER>
TMP_BUFFERS=$<TARGET_PROPERTY:TMP_BUFFERS>
)
在上面,PROTOCOL_VER被定义为最小数字接口兼容性,因此PROTOCOL_VER的属性将被设置为为其链接到的库的属性MyApp指定的最小值,在本例中为 2。同样,被定义为最大数字接口兼容性,该属性在其链接库的属性中获得最大的值
,为 200。INTERFACE_PROTOCOL_VERTMP_BUFFERSMyApp
TMP_BUFFERSINTERFACE_TMP_BUFFERS
In the above, PROTOCOL_VER is defined as a minimum numeric interface
compatibility, so the PROTOCOL_VER property of MyApp will be set to the
smallest value specified for the INTERFACE_PROTOCOL_VER property of the
libraries it links to, which in this case is 2. Similarly, TMP_BUFFERS is
defined as a maximum numeric interface compatibility and the MyApp
TMP_BUFFERS property receives the largest value among the
INTERFACE_TMP_BUFFERS property of its linked libraries, which is 200.
此时,很自然地会考虑对最小和最大数字接口兼容性使用相同的属性,以允许在父级中检测到最小和最大值。这是不可能的,因为 CMake 不允许(也不能)允许将同一属性与多种接口兼容性一起使用。如果某个属性用于多种类型的接口兼容性,则 CMake 不可能知道应使用哪种类型来计算要存储在父级结果属性中的值。例如,如果上面的示例中既有最小接口兼容性又有最大接口兼容性,则 CMake 无法确定要存储在属性中的PROTOCOL_VER值- 应该存储最小值还是最大值?相反,必须使用单独的属性来实现此目的:PROTOCOL_VERMyApp
At this point, it would be natural to think about using the same property for
both a minimum and maximum numeric interface compatibility to allow both the
smallest and largest value to be detected in the parent. This is not possible
because CMake does not (and cannot) allow the same property to be used with
more than one kind of interface compatibility. If a property was used for
multiple types of interface compatibilities, it would be impossible for CMake
to know which type should be used to compute the value to be stored in the
parent’s result property. For example, if PROTOCOL_VER were both a minimum
and maximum interface compatibility in the above example, CMake could not
determine the value to store in the PROTOCOL_VER property of MyApp - should
it store the minimum or maximum value? Instead, separate properties must be
used to achieve this:
add_library(BigFast strategy1.cpp)
set_target_properties(BigFast PROPERTIES
COMPATIBLE_INTERFACE_NUMBER_MIN PROTOCOL_VER_MIN
COMPATIBLE_INTERFACE_NUMBER_MAX PROTOCOL_VER_MAX
INTERFACE_PROTOCOL_VER_MIN 3
INTERFACE_PROTOCOL_VER_MAX 3
)
add_library(SmallSlow strategy2.cpp)
set_target_properties(SmallSlow PROPERTIES
COMPATIBLE_INTERFACE_NUMBER_MIN PROTOCOL_VER_MIN
COMPATIBLE_INTERFACE_NUMBER_MAX PROTOCOL_VER_MAX
INTERFACE_PROTOCOL_VER_MIN 2
INTERFACE_PROTOCOL_VER_MAX 2
)
add_executable(MyApp myapp.cpp)
target_link_libraries(MyApp PRIVATE BigFast SmallSlow)
target_compile_definitions(MyApp PRIVATE
PROTOCOL_VER_MIN=$<TARGET_PROPERTY:PROTOCOL_VER_MIN>
PROTOCOL_VER_MAX=$<TARGET_PROPERTY:PROTOCOL_VER_MAX>
)add_library(BigFast strategy1.cpp)
set_target_properties(BigFast PROPERTIES
COMPATIBLE_INTERFACE_NUMBER_MIN PROTOCOL_VER_MIN
COMPATIBLE_INTERFACE_NUMBER_MAX PROTOCOL_VER_MAX
INTERFACE_PROTOCOL_VER_MIN 3
INTERFACE_PROTOCOL_VER_MAX 3
)
add_library(SmallSlow strategy2.cpp)
set_target_properties(SmallSlow PROPERTIES
COMPATIBLE_INTERFACE_NUMBER_MIN PROTOCOL_VER_MIN
COMPATIBLE_INTERFACE_NUMBER_MAX PROTOCOL_VER_MAX
INTERFACE_PROTOCOL_VER_MIN 2
INTERFACE_PROTOCOL_VER_MAX 2
)
add_executable(MyApp myapp.cpp)
target_link_libraries(MyApp PRIVATE BigFast SmallSlow)
target_compile_definitions(MyApp PRIVATE
PROTOCOL_VER_MIN=$<TARGET_PROPERTY:PROTOCOL_VER_MIN>
PROTOCOL_VER_MAX=$<TARGET_PROPERTY:PROTOCOL_VER_MAX>
)
上面示例的结果是MyApp根据其链接到的库使用的协议了解其需要支持的协议版本范围。
The result of the above example is that MyApp knows the range of protocol
versions it needs to support based on the protocols used by the libraries it
links to.
如果一个目标定义了任何特定类型的接口兼容性,则其他目标不需要也定义它。对于该特定属性,任何未定义匹配接口兼容性的目标都会被忽略。这确保库只需要定义与其相关的接口兼容性。
If one target defines an interface compatibility of any particular type, other targets are not required to define it too. Any target which does not define a matching interface compatibility is simply ignored for that particular property. This ensures libraries only need to define interface compatibilities that are relevant to them.
当存在多个级别的库链接依赖性时,如何处理接口兼容性会出现一些微妙的复杂性。考虑下图所示的结构,其中包含许多库和可执行目标及其直接链接依赖项。
When there are multiple levels of library link dependencies, there are some subtle complexities to how interface compatibilities are handled. Consider the structure shown in the following diagram, which contains a number of library and executable targets and their direct link dependencies.
如果考虑所有链接依赖关系PRIVATE,则只有libNet和
libUtil是 的直接链接依赖关系MyApp,因此只需要这两个库的属性值保持一致INTERFACE_FOO。不考虑库中该属性的值libCalc,因为它不是 的直接依赖项MyApp。此外, 的唯一直接链接依赖项libUtil是libCalc,因此INTERFACE_FOO的属性
libCalc不需要与其他库保持一致。尽管 和 都libUtil为libCalc相同的属性名称定义了接口兼容性,但由于它们不是共同目标的直接链接依赖项,因此它们不需要具有兼容的值。
If all link dependencies are considered PRIVATE, then only libNet and
libUtil are direct link dependencies of MyApp, so only those two libraries
are required to have consistent values for their INTERFACE_FOO property. The
value of that property in the libCalc library is not considered, since it is
not a direct dependency of MyApp. Furthermore, the only direct link
dependency of libUtil is libCalc, so the INTERFACE_FOO property of
libCalc has no other library it is required to be consistent with. Even
though both libUtil and libCalc define an interface compatibility for the
same property name, because they are not both direct link dependencies of a
common target, they are not required to have compatible values.
libCalc现在考虑 的链接PUBLIC依赖性的
情况libUtil。在这种情况下,最终的链接关系实际上将如下所示:
Now consider the situation where libCalc is a PUBLIC link dependency of
libUtil. In that case, the final linking relationships will actually look
like this:
当libCalc是 的PUBLIC链接依赖性时libUtil,链接到的任何内容libUtil也将链接到libCalc。因此,libCalc成为 的直接链接依赖项MyApp,因此它确实参与libNet和的接口兼容性检查libUtil。PUBLIC这意味着在定义接口兼容性时必须非常小心,以确保它们准确地表达正确的内容,因为当涉及链接关系时,它们的范围可能会扩展到超出最初看起来显而易见的目标。
When libCalc is a PUBLIC link dependency of libUtil, anything that links
to libUtil will also link to libCalc. Thus, libCalc becomes a direct link
dependency of MyApp and therefore it does participate in interface
compatibility checking with libNet and libUtil. This means great care must
taken when defining interface compatibilities to ensure that they accurately
express the correct things, since their reach can extend out to targets beyond
what may initially seem obvious when PUBLIC link relationships are involved.
简单地说,库可以被认为是已编译源代码的容器,提供其他代码可以调用或使用的各种函数和全局数据。对于静态库,容器实际上只是对象文件的集合,将其组合在一起的工具有时称为归档器或库管理器。另一方面,共享库由链接器生成,链接器处理目标代码、档案等,并决定在最终共享库二进制文件中包含哪些内容。某些函数和全局数据可能是隐藏的,这意味着它们已被标记为可以供链接器用来解决内部代码依赖性,但共享库外部的代码无法调用或使用它们。其他符号被导出,因此共享库内部和外部的代码都可以访问它们。这称为符号的可见性。
Simplistically, a library can be thought of as a container of compiled source code, providing various functions and global data which other code can call or use. For static libraries, the container is really just a collection of object files and the tool putting it together is sometimes referred to as an archiver or librarian. Shared libraries, on the other hand, are produced by the linker, which processes the object code, archives, etc. and decides what to include in the final shared library binary. Some functions and global data may be hidden, meaning they have been marked as okay for the linker to use to resolve internal code dependencies, but code outside of the shared library cannot call or use them. Other symbols are exported, so code both inside and outside of the shared library can access them. This is referred to as a symbol’s visibility.
编译器有不同的方式来指定符号可见性,并且它们也有不同的默认行为。有些默认情况下使所有符号可见,而另一些则默认隐藏符号。编译器在用于将各个函数、类和数据标记为可见或不可见的语法方面也有所不同,这增加了编写可移植共享库的复杂性。为了避免这种复杂性,一些开发人员选择简单地使所有符号可见,并避免必须显式标记任何符号以供导出。虽然这最初看起来像是一场胜利,但它也有一系列缺点:
Compilers have different ways of specifying symbol visibility and they also have different default behaviors. Some make all symbols visible by default, whereas others hide symbols by default. Compilers also differ in the syntax used to mark individual functions, classes and data as visible or not, which adds to the complexity of writing portable shared libraries. In order to avoid some of that complexity, some developers opt to simply make all symbols visible and avoid having to explicitly mark any symbols for export. While this may initially seem like a win, it comes with a range of down sides:
上述几点强调了符号可见性不仅与共享库性能和包大小的低级机制有关,还与强制库 API 的公私性质有关。显然,仅导出那些应被视为公共的符号有其优点,但如何实现这一点的编译器和平台特定性质通常会给多平台项目带来重大障碍。CMake 通过抽象出一些属性、变量和辅助模块背后的这些差异,大大简化了这个过程。
The above points highlight that symbol visibility is as much about enforcing the public-private nature of a library’s API as it is about the low level mechanics of shared library performance and package size. Clearly, there are advantages to only exporting those symbols which should be considered public, but the compiler and platform specific nature of how to achieve that often presents a substantial hurdle for multi-platform projects. CMake considerably simplifies this process by abstracting away those differences behind a few properties, variables and a helper module.
默认情况下,Visual Studio 编译器假设所有符号都是隐藏的,除非显式导出。其他编译器(例如 GCC 和 Clang)则相反,默认情况下使所有符号可见,并且仅在明确告知时才隐藏符号。如果项目希望在所有编译器和平台上具有相同的默认符号可见性,则必须选择这两种方法之一,但希望上一节中强调的缺点为选择默认隐藏符号提供令人信服的论据。
By default, Visual Studio compilers assume all symbols are hidden unless explicitly exported. Other compilers, such as GCC and Clang are the opposite, making all symbols visible by default and only hiding symbols if explicitly told to. If a project wishes to have the same default symbol visibility across all its compilers and platforms, one of these two approaches must be selected, but hopefully the disadvantages highlighted in the preceding section provide a compelling argument for choosing that symbols be hidden by default.
强制执行隐藏默认可见性的第一步是
<LANG>_VISIBILITY_PRESET在共享库目标上定义属性集。
对于使用此功能的两种最常见语言, C 和 C++ 的属性名称分别为C_VISIBILITY_PRESET和。CXX_VISIBILITY_PRESET赋予该属性的值应该是
hidden,这会更改默认可见性以隐藏所有符号。其他支持的值包括default、protected和internal,但这些值对于跨平台项目不太可能有用。它们要么指定已经是默认行为的行为,要么是hidden在某些上下文中具有更专门含义的变体。
The first step to enforcing hidden default visibility is to define the
<LANG>_VISIBILITY_PRESET set of properties on a shared library target.
For the two most common languages where this functionality is used, the
property names are C_VISIBILITY_PRESET and CXX_VISIBILITY_PRESET
for C and C++ respectively. The value given to this property should be
hidden, which changes the default visibility to hide all symbols. Other
supported values include default, protected and internal, but these are
less likely to be useful for cross-platform projects. They either specify what
is already the default behavior or are variants of hidden with more
specialized meanings in some contexts.
第二步是指定默认情况下也应隐藏内联函数。对于大量使用模板的 C++ 代码,这可以大大减少最终共享库二进制文件的大小。此行为由目标属性控制VISIBILITY_INLINES_HIDDEN并适用于所有语言。它应该保存布尔值TRUE以默认隐藏内联符号。
The second step is to specify that inlined functions should also be hidden by
default. For C++ code making heavy use of templates, this can substantially
reduce the size of the final shared library binary. This behavior is controlled
by the target property VISIBILITY_INLINES_HIDDEN and applies to all
languages. It should hold the boolean value TRUE to hide inline symbols by
default.
两者<LANG>_VISIBILITY_PRESET都VISIBILITY_INLINES_HIDDEN可以在每个共享库目标上指定,或者可以通过适当的 CMake 变量设置默认值。创建目标时,其
<LANG>_VISIBILITY_PRESET属性由 CMake 变量的值初始化CMAKE_<LANG>_VISIBILITY_PRESET,并且其
VISIBILITY_INLINES_HIDDEN属性由该变量初始化
CMAKE_VISIBILITY_INLINES_HIDDEN。这通常比单独设置每个目标的属性更方便。
Both <LANG>_VISIBILITY_PRESET and VISIBILITY_INLINES_HIDDEN can be
specified on each shared library target, or a default can be set by the
appropriate CMake variables. When a target is created, its
<LANG>_VISIBILITY_PRESET property is initialized by the value of the CMake
variable CMAKE_<LANG>_VISIBILITY_PRESET and its
VISIBILITY_INLINES_HIDDEN property is initialized by the
CMAKE_VISIBILITY_INLINES_HIDDEN variable. This is typically more
convenient than setting the properties for each target individually.
对于那些希望使所有符号在所有平台上默认可见的项目,这只需要更改 Visual Studio 编译器的默认行为。从版本 3.4 开始,CMake 提供了
WINDOWS_EXPORT_ALL_SYMBOLS提供此行为的目标属性,但有一些警告。将此属性定义为 true 值将导致 CMake 写入一个.def文件,其中包含用于创建共享库的所有对象文件中的所有符号,并将该.def文件传递给链接器。这是一种相当强力的方法,可以防止源代码有选择地隐藏任何符号,因此仅应在所有符号都可见的情况下使用它。CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS创建共享库目标时,此目标属性由 CMake 变量初始化
。
For those projects wishing to make all symbols visible by default across all
platforms, this only requires changing the default behavior of Visual Studio
compilers. From version 3.4, CMake provides the
WINDOWS_EXPORT_ALL_SYMBOLS target property which provides this behavior,
but with caveats. Defining this property to a true value will cause CMake to
write a .def file containing all symbols from all object files used to create
the shared library and pass that .def file to the linker. This is a fairly
brute force method which prevents the source code from selectively hiding any
symbols, so it should only be used where all symbols should be made visible.
This target property is initialized by the
CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS CMake variable when a shared library
target is created.
最常见的编译器支持指定单个符号的可见性,但它们的方式各不相同。一般来说,Visual Studio 使用一种方法,大多数其他编译器都遵循 GCC 使用的方法。两者具有相似的结构,但使用不同的关键字。这意味着 C、C++ 及其衍生语言等语言的源代码可以使用通用预处理器定义来进行可见性控制,并且项目可以指示 CMake 提供适当的定义。
Most common compilers support specifying the visibility of individual symbols, but the way they do so varies. In general Visual Studio uses one method and most other compilers follow the method used by GCC. The two share a similar structure, but they use different keywords. This means source code for languages like C, C++ and their derivatives can use a common preprocessor define for visibility control and projects can instruct CMake to provide the appropriate definition.
可以指定符号可见性的三种主要情况:类、函数和变量。在以下包含这三种情况的声明的示例中,请注意 的位置
MYTOOLS_EXPORT:
There are three primary cases where symbol visibility can be specified:
classes, functions and variables. In the following example which contains
declarations for each of these three cases, note the position of
MYTOOLS_EXPORT:
// Export non-private members of a class
class MYTOOLS_EXPORT SomeClass {...};
// Make a free function visible
MYTOOLS_EXPORT void someFunction();
// Make a global variable visible
MYTOOLS_EXPORT extern int myGlobalVar;// Export non-private members of a class
class MYTOOLS_EXPORT SomeClass {...};
// Make a free function visible
MYTOOLS_EXPORT void someFunction();
// Make a global variable visible
MYTOOLS_EXPORT extern int myGlobalVar;
当构建包含上述实现的共享库时,
需要用相关关键字替换,指定应导出MYTOOLS_EXPORT符号以供其他库和可执行文件使用。另一方面,如果相同的声明由属于共享库之外的某个其他目标的代码读取,则必须用指定应导入该符号的相关关键字替换
。在 Windows 上,这些关键字采用 的形式,而 GCC 和兼容编译器使用.MYTOOLS_EXPORT__declspec(...)__attribute__(...)
When building the shared library containing the implementations of the above,
MYTOOLS_EXPORT needs to be substituted with the relevant keywords specifying
that the symbol should be exported for other libraries and executables to
use. On the other hand, if the same declarations are read by code belonging to
some other target outside of the shared library, then MYTOOLS_EXPORT must be
substituted with the relevant keywords specifying that the symbol should be
imported. On Windows, these keywords take the form __declspec(...),
whereas GCC and compatible compilers use __attribute__(...).
MYTOOLS_EXPORT为所有编译器以及导出和导入情况
提供正确的内容可能会有些混乱。再加上开发人员可能选择构建共享库或静态库,复杂性就会增加。值得庆幸的是,CMake 提供了
GenerateExportHeader以非常方便的方式处理所有这些细节的模块。该模块提供以下功能:
Coming up with the right contents for MYTOOLS_EXPORT for all compilers and
for both the exporting and importing cases can be somewhat messy. Add into the
mix that developers might choose to build a library as either shared or static
and the complexity grows. Thankfully, CMake provides the
GenerateExportHeader module which handles all of these details in a very
convenient fashion. This module provides the following function:
generate_export_header(target
[BASE_NAME baseName]
[EXPORT_FILE_NAME exportFileName]
[EXPORT_MACRO_NAME exportMacroName]
[DEPRECATED_MACRO_NAME deprecatedMacroName]
[NO_EXPORT_MACRO_NAME noExportMacroName]
[STATIC_DEFINE staticDefine]
[NO_DEPRECATED_MACRO_NAME noDeprecatedMacroName]
[DEFINE_NO_DEPRECATED]
[PREFIX_NAME prefix]
[CUSTOM_CONTENT_FROM_VARIABLE var]
)generate_export_header(target
[BASE_NAME baseName]
[EXPORT_FILE_NAME exportFileName]
[EXPORT_MACRO_NAME exportMacroName]
[DEPRECATED_MACRO_NAME deprecatedMacroName]
[NO_EXPORT_MACRO_NAME noExportMacroName]
[STATIC_DEFINE staticDefine]
[NO_DEPRECATED_MACRO_NAME noDeprecatedMacroName]
[DEFINE_NO_DEPRECATED]
[PREFIX_NAME prefix]
[CUSTOM_CONTENT_FROM_VARIABLE var]
)
通常,不需要任何可选参数,仅提供共享库目标名称。CMake 在当前二进制目录中写出一个头文件,使用小写的目标名称并_export.h附加作为头文件名。标头提供了具有类似结构名称的符号导出的定义,这次使用
_EXPORT附加的大写目标名称。下面演示了这种典型用法:
Typically, none of the optional arguments are needed and only the shared
library target name is provided. CMake writes out a header file in the current
binary directory, using the target name in lowercase with _export.h appended
as the header file name. The header provides a define for symbol export with a
similarly structured name, this time using the uppercase target name with
_EXPORT appended. The following demonstrates this typical usage:
# Hide things by default
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
# NOTE: myTools.cpp must #include myTools.h
add_library(MyTools myTools.cpp)
target_include_directories(MyTools PUBLIC
"${CMAKE_CURRENT_BINARY_DIR}"
)
# Write mytools_export.h to the current binary directory
include(GenerateExportHeader)
generate_export_header(MyTools)# Hide things by default
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
# NOTE: myTools.cpp must #include myTools.h
add_library(MyTools myTools.cpp)
target_include_directories(MyTools PUBLIC
"${CMAKE_CURRENT_BINARY_DIR}"
)
# Write mytools_export.h to the current binary directory
include(GenerateExportHeader)
generate_export_header(MyTools)
#include "mytools_export.h"
class MYTOOLS_EXPORT SomeClass
{
// ...
};
MYTOOLS_EXPORT void someFunction();
MYTOOLS_EXPORT extern int myGlobalVar;#include "mytools_export.h"
class MYTOOLS_EXPORT SomeClass
{
// ...
};
MYTOOLS_EXPORT void someFunction();
MYTOOLS_EXPORT extern int myGlobalVar;
当前二进制目录不是默认标头搜索路径的一部分,因此需要将其添加为PUBLIC库的搜索路径,以确保
mytools_export.h库自己的源代码和链接到目标的任何其他代码都可以找到标头共享库。
The current binary directory is not part of the default header search path, so
it needs to be added as a PUBLIC search path for the library to ensure the
mytools_export.h header can be found by both the library’s own source code
and any other code from targets linking to the shared library.
如果不希望使用目标名称作为头文件名或预处理器定义名称的一部分,则BASE_NAME可以使用该选项来提供替代方案。它以相同的方式进行转换,转换为小写并_export.h附加文件名,大写_EXPORT
附加预处理器定义。
If using the target name as part of the header file name or preprocessor define
name is not desirable, the BASE_NAME option can be used to provide an
alternative. It is transformed in the same way, being converted to lowercase
and having _export.h appended for the file name and uppercase with _EXPORT
appended for the preprocessor define.
include(GenerateExportHeader)
generate_export_header(MyTools BASE_NAME fooBar)include(GenerateExportHeader)
generate_export_header(MyTools BASE_NAME fooBar)
#include "foobar_export.h"
class FOOBAR_EXPORT SomeClass
{
// ...
};
FOOBAR_EXPORT void someFunction();
FOOBAR_EXPORT extern int myGlobalVar;#include "foobar_export.h"
class FOOBAR_EXPORT SomeClass
{
// ...
};
FOOBAR_EXPORT void someFunction();
FOOBAR_EXPORT extern int myGlobalVar;
如果文件和预处理器定义应使用不同的名称,则
可以给出和选项BASE_NAME,而不是使用 。与 不同的是,这两个选项提供的名称无需任何修改即可使用。EXPORT_FILE_NAMEEXPORT_MACRO_NAMEBASE_NAME
If a different name should be used for the file and preprocessor define, then
rather than using BASE_NAME, the EXPORT_FILE_NAME and EXPORT_MACRO_NAME
options can be given. Unlike BASE_NAME, the names provided by these two
options are used without any modification.
include(GenerateExportHeader)
generate_export_header(MyTools
EXPORT_FILE_NAME export_myTools.h
EXPORT_MACRO_NAME API_MYTOOLS
)include(GenerateExportHeader)
generate_export_header(MyTools
EXPORT_FILE_NAME export_myTools.h
EXPORT_MACRO_NAME API_MYTOOLS
)
#include "export_myTools.h"
class API_MYTOOLS SomeClass
{
// ...
};
API_MYTOOLS void someFunction();
API_MYTOOLS extern int myGlobalVar;#include "export_myTools.h"
class API_MYTOOLS SomeClass
{
// ...
};
API_MYTOOLS void someFunction();
API_MYTOOLS extern int myGlobalVar;
该generate_export_header()函数不仅提供了这一预处理器定义,还提供了其他预处理器定义,这些定义可用于将符号标记为已弃用或显式指定永远不应导出符号。后者可用于防止导出类中原本已导出的部分,例如供共享库内部使用但不能由其外部代码使用的公共成员函数。默认情况下,此预处理器定义的名称由附加的目标名称(或BASE_NAME如果指定)组成_NO_EXPORT,但如果需要,可以使用该选项提供替代名称NO_EXPORT_MACRO_NAME。
The generate_export_header() function provides more than just this one
preprocessor define, it also provides other preprocessor definitions which can
be used to mark symbols as deprecated or to explicitly specify that a symbol
should never be exported. The latter can be useful to prevent exporting parts
of a class that is otherwise exported, such as a public member function
intended for internal use within the shared library but not by code outside it.
By default, the name of this preprocessor definition consists of the target
name (or BASE_NAME if it is specified) with _NO_EXPORT appended, but an
alternative name can be provided with the NO_EXPORT_MACRO_NAME option if
desired.
include(GenerateExportHeader)
generate_export_header(MyTools
NO_EXPORT_MACRO_NAME REALLY_PRIVATE
)include(GenerateExportHeader)
generate_export_header(MyTools
NO_EXPORT_MACRO_NAME REALLY_PRIVATE
)
#include "mytools_export.h"
class MYTOOLS_EXPORT SomeClass
{
public:
REALLY_PRIVATE void doInternalThings();
// ...
};#include "mytools_export.h"
class MYTOOLS_EXPORT SomeClass
{
public:
REALLY_PRIVATE void doInternalThings();
// ...
};
该函数的弃用支持以类似的方式工作,提供预处理器定义,其中包含大写的目标(或BASE_NAME)名称,后跟_DEPRECATED,或允许通过选项指定自定义名称
DEPRECATED_MACRO_NAME。也可以给出该DEFINE_NO_DEPRECATED选项,这将导致为附加的预处理器定义提供一个由通常的大写目标或BASE_NAME后跟 组成的名称_NO_DEPRECATED。与其他预处理器定义一样,该名称也可以使用NO_DEPRECATED_MACRO_NAME选项覆盖。对于某些编译器,标记为已弃用的符号可能会导致编译时警告,从而引起人们对其使用的注意。这可能是一种有用的机制,可以鼓励开发人员更新其代码以不再使用已弃用的符号。下面显示了如何使用弃用机制。
The function’s deprecation support works in a similar way, providing a
preprocessor definition with the uppercased target (or BASE_NAME) name
followed by _DEPRECATED, or allowing a custom name to be specified via the
DEPRECATED_MACRO_NAME option. The DEFINE_NO_DEPRECATED option can also be
given, which will result in an additional preprocessor define being provided
with a name consisting of the usual uppercased target or BASE_NAME followed
by _NO_DEPRECATED. Like the other preprocessor defines, this name can also be
overridden with the NO_DEPRECATED_MACRO_NAME option. With some compilers,
symbols marked as deprecated can result in compile time warnings which draw
attention to their use. This can be a helpful mechanism to encourage developers
to update their code to no longer use the deprecated symbols. The following
shows how the deprecation mechanisms can be used.
option(OMIT_DEPRECATED "Omit deprecated parts of MyTools")
if(OMIT_DEPRECATED)
set(deprecatedOption "DEFINE_NO_DEPRECATED")
else()
unset(deprecatedOption)
endif()
include(GenerateExportHeader)
generate_export_header(MyTools
NO_DEPRECATED_MACRO_NAME OMIT_DEPRECATED
${deprecatedOption}
)option(OMIT_DEPRECATED "Omit deprecated parts of MyTools")
if(OMIT_DEPRECATED)
set(deprecatedOption "DEFINE_NO_DEPRECATED")
else()
unset(deprecatedOption)
endif()
include(GenerateExportHeader)
generate_export_header(MyTools
NO_DEPRECATED_MACRO_NAME OMIT_DEPRECATED
${deprecatedOption}
)
#include "mytools_export.h"
class MYTOOLS_EXPORT SomeClass
{
public:
#ifndef OMIT_DEPRECATED
MYTOOLS_DEPRECATED void oldImpl();
#endif
// ...
};#include "mytools_export.h"
class MYTOOLS_EXPORT SomeClass
{
public:
#ifndef OMIT_DEPRECATED
MYTOOLS_DEPRECATED void oldImpl();
#endif
// ...
};
#include "myTools.h"
#ifndef OMIT_DEPRECATED
void SomeClass::oldImpl() { ... }
#endif#include "myTools.h"
#ifndef OMIT_DEPRECATED
void SomeClass::oldImpl() { ... }
#endif
上面的示例提供了一个 CMake 缓存变量来确定是否编译已弃用的项目。开发人员无需编辑任何文件即可做出此选择,因此无论是否已弃用 API 部分,验证行为都很容易。如果已设置持续集成构建来测试是否包含库中已弃用的部分,则这会特别有用。在项目被用作另一个项目的依赖项的情况下,它也很有用,允许其他项目的开发人员仅通过更改 CMake 缓存变量来测试他们的代码是否使用已弃用的符号。
The above example provides a CMake cache variable to determine whether or not to compile the deprecated items. The developer has the ability to make this choice without editing any files, so verifying behavior with or without the deprecated part of an API is easy to do. This can be particularly useful if continuous integration builds have been set up to test both with and without deprecated parts of a library. It can also be useful in situations where the project is being used as a dependency of another project, allowing that other project’s developers to test whether their code uses the deprecated symbols or not just by changing the CMake cache variable.
一个不太常见但仍然重要的案例也值得特别提及。某些项目可能希望构建同一库的共享版本和静态版本。在这种情况下,同一组源代码需要允许为共享库构建启用符号导出,但为静态库构建禁用符号导出(另请参阅下一节,了解为什么情况并非总是如此)。当一个构建中需要两种形式的库时,它们需要是不同的构建目标,但该generate_export_header()函数会写入与单个目标紧密相关的标头。为了支持这一场景,生成的标头包含用于在填充导出定义之前检查是否存在另一个预处理器定义的逻辑。这个特殊定义的名称再次遵循通常的模式,这次是大写的目标或BASE_NAME后跟_STATIC_DEFINE,或者具有由选项提供的自定义名称STATIC_DEFINE。当定义这个特殊的预处理器定义时,导出定义将被迫扩展为空,这通常是在将目标构建为静态库时所需要的。如果没有特殊的预处理器定义,导出定义将具有常规内容,并在构建共享库目标时按预期工作。
A less common but nevertheless important case also deserves special mention.
Some projects may wish to build both shared and static versions of the same
library. In this case, the same set of source code needs to allow symbol
exports to be enabled for the shared library build, but disabled for the static
library build (also see the next section for why this won’t always be the
case). When both forms of library are required in the one build, they need to
be different build targets, but the generate_export_header() function writes
a header that is closely tied to a single target. In order to support this
scenario, the generated header includes logic to check for the existence of one
further preprocessor define before populating the export definition. The name
of this special define follows the usual pattern once again, this time being
the uppercased target or BASE_NAME followed by _STATIC_DEFINE, or having a
custom name provided by the STATIC_DEFINE option. When this special
preprocessor definition is defined, the export definition is forced to expand
to nothing, which is typically what is needed when the target is being built as
a static library. Without the special preprocessor definition, the export
define has the usual contents and works as expected when building a shared
library target.
当为同一组源文件构建共享库和静态库时,generate_export_header()应该为该函数提供与共享库对应的目标。然后,仅在静态库的目标上设置特殊的预处理器定义。该BASE_NAME选项通常还用于使各种符号对于任一形式的库来说都是直观的,而不是仅特定于共享库。下面演示了实现预期结果所需的结构:
When both shared and static libraries are being built for the same set of
source files, the generate_export_header() function should be given the
target that corresponds to the shared library. The special preprocessor define
is then set only on the static library’s target. The BASE_NAME option will
also typically be used to make the various symbols intuitive to either form of
the library rather than being specific to the shared library only. The
following demonstrates the structure needed to achieve the desired result:
# Same source list, different library types
add_library(MyShared SHARED ${mySources})
add_library(MyStatic STATIC ${mySources})
# Shared target used for generating export header
# with the name mytools_export.h, which will be suitable
# for both the shared and static targets
include(GenerateExportHeader)
generate_export_header(MyShared BASE_NAME MyTools)
# Static target needs special preprocessor define
# to prevent symbol import/export keywords being added
target_compile_definitions(MyStatic PRIVATE
MYTOOLS_STATIC_DEFINE
)# Same source list, different library types
add_library(MyShared SHARED ${mySources})
add_library(MyStatic STATIC ${mySources})
# Shared target used for generating export header
# with the name mytools_export.h, which will be suitable
# for both the shared and static targets
include(GenerateExportHeader)
generate_export_header(MyShared BASE_NAME MyTools)
# Static target needs special preprocessor define
# to prevent symbol import/export keywords being added
target_compile_definitions(MyStatic PRIVATE
MYTOOLS_STATIC_DEFINE
)
从前面的讨论可以明显看出,该generate_export_header()
函数定义了许多不同的预处理器定义,并且不同的目标有机会意外地尝试对其中至少一些使用相同的名称。为了帮助减少名称冲突,该PREFIX_NAME
选项允许指定一个附加字符串,该字符串将添加到每个预处理器定义的名称前面。使用时,此选项通常与整个项目相关,有效地将项目生成的所有预处理器名称放入特定于项目的命名空间之类的内容中。
As is evident by the preceding discussion, the generate_export_header()
function defines a number of different preprocessor definitions and there are
opportunities for different targets to accidentally try to use the same names
for at least some of them. To help reduce name collisions, the PREFIX_NAME
option allows an additional string to be specified which will be prepended to
the names of each preprocessor definition. When used, this option would
typically be something related to the project as a whole, effectively putting
all of a project’s generated preprocessor names into something like a
project-specific namespace.
最后一个尚未讨论的选项是CUSTOM_CONTENT_FROM_VARIABLE,它仅在 CMake 3.7 中添加。在添加所有各种预处理器逻辑之后,此选项允许将任意内容注入到生成的标头附近的末尾。使用时,必须为该选项指定要注入其内容的变量的名称,而不是内容本身。
The last option not yet discussed is CUSTOM_CONTENT_FROM_VARIABLE, which was
only added in CMake 3.7. This option allows arbitrary content to be injected
into the generated header near the end, after all of the various preprocessor
logic has been added. When used, this option must be given the name of a
variable whose contents should be injected, not the content itself.
string(TIMESTAMP now)
set(customContents "/* Generated: ${now} */")
generate_export_header(MyTools
CUSTOM_CONTENT_FROM_VARIABLE customContents
)string(TIMESTAMP now)
set(customContents "/* Generated: ${now} */")
generate_export_header(MyTools
CUSTOM_CONTENT_FROM_VARIABLE customContents
)
当项目将其所有库构建为静态时,构建可能看起来对库链接依赖关系更加宽容。项目可能会忽略指定一个目标需要另一个目标,但是当各种静态库链接到最终的可执行文件时,缺少的库依赖项会得到满足,因为它们是按所需顺序显式列出的可执行文件。然后构建会成功,但可能只有在进行构建一段时间的尝试和错误之后,让链接器抱怨丢失的符号,添加更多丢失的库或重新排序现有的库等。
When a project builds all its libraries as static, the build may appear to be a bit more forgiving about library link dependencies. The project may neglect to specify that one target requires another, but when various static libraries are linked into a final executable, the missing library dependencies are satisfied because they are explicitly listed for the executable in the required order. The build then succeeds, but probably only after a period of trial and error doing builds, having the linker complain about missing symbols, adding in more missing libraries or reordering the existing ones, etc.
这种情况的成功更多地取决于运气,而不是良好的设计,但这种情况非常常见,特别是对于定义许多小型库的项目。如果至少为某些静态库指定了链接依赖项,CMake 会自动处理传递链接这些依赖项,因此即使错误指定了依赖项的PRIVATE/PUBLIC性质,对于静态库,它也始终被视为无论如何PUBLIC,这有时会导致构建即使链接依赖性没有准确描述,它仍然可以工作。
This scenario results in success more by good fortune than by good design, but
it is surprisingly common, especially with projects that define many small
libraries. If link dependencies are specified for at least some of the static
libraries, CMake automatically handles transitively linking those dependencies,
so even if the PRIVATE/PUBLIC nature of the dependency is specified
incorrectly, with a static library it is always treated as PUBLIC anyway and
this sometimes makes builds work even though the link dependency isn’t
accurately described.
当库目标被定义为共享和静态的混合时,链接依赖关系的正确性变得更加重要。考虑以下一组目标:
When library targets are defined as a mix of shared and static, the correctness of link dependencies becomes much more important. Consider the following set of targets:
如果libUtil和libCalc是静态库,则上述链接依赖关系是安全的。如果libUtil是一个共享库,则上述链接依赖性安排开启了复制数据的可能性,预计整个应用程序中只有一个实例。如果libCalc定义了全局数据,例如对于类的单例或静态数据来说可能是常见的,则MyApp和 都有可能libUtil拥有它们自己的单独的数据实例。这是可能的,因为 和MyApp都libUtil需要链接器解析符号,因此两个调用都可能决定需要全局数据并在该可执行文件或共享库中设置它的内部实例。libUtil如果全局数据不是导出的符号,则链接器在转到 link 时将看不到已创建的实例MyApp。最终结果是在 中创建了第二个实例MyApp,这几乎肯定会导致难以跟踪的运行时问题。这种情况的典型表现是变量神奇地出现在从一个可执行文件或共享库到另一个共享库的函数调用中更改值的情况。
If libUtil and libCalc are static libraries, the above link dependency
relationships are safe. If libUtil is a shared library, then the above link
dependency arrangement opens up the possibility of duplicating data expected to
have only one instance across a whole application. If libCalc defines global
data, such as might be common for a singleton or static data of a class, it may
be possible for both MyApp and libUtil to have their own separate instances
of that data. This becomes possible because both MyApp and libUtil require
the linker to resolve symbols, so both invocations may decide the global data
is required and set up an internal instance of it within that executable or
shared library. If the global data is not an exported symbol, the linker won’t
see the instance already created in libUtil when it goes to link MyApp.
The end result is that a second instance is created in MyApp, which is almost
certain to cause hard-to-trace runtime issues. A typical manifestation of this
is a variable magically appearing to change values across a function call from
one executable or shared library into another shared library.
与上述场景类似的情况可以以多种不同的形式出现,但相同的基本原则适用于每种情况。如果静态库链接到共享库,则该共享库不应与也链接到同一静态库的任何其他库或可执行文件组合。理想情况下,如果共享库和静态库混合在一起,那么静态库应该只专门链接到一个共享库,而任何需要这些静态库之一的东西都应该链接到共享库。共享库本质上有自己的 API,静态库可能会对其做出贡献。
Situations similar to the above scenario can appear in a number of different forms, but the same underlying principle applies in each case. If a static library is linked into a shared library, that shared library should not be combined with any other library or executable that also links to that same static library. Ideally, if shared and static libraries are being mixed, then the static libraries should only ever exclusively be linked into one shared library and anything that needs something from one of those static libraries should link to the shared library instead. The shared library essentially has its own API and the static libraries may contribute to it.
当涉及到符号可见性时,使用静态库构建这样的共享库内容会带来一系列问题。通常,静态库中的代码不会被导出,因此它不会作为共享库的导出符号的一部分出现。解决此问题的一种方法是正常使用
generate_export_header()共享库上的函数,然后使静态库重新使用相同的导出定义。完成这项工作的关键是确保静态库具有附加的共享库目标名称的编译定义_EXPORTS,这就是生成的标头如何检测代码是否被构建为共享库的一部分。
Using static libraries to build up shared library content like this presents
its own set of issues when it comes to symbol visibility. Ordinarily, the code
from the static libraries would not be exported, so it would not appear as part
of the shared library’s exported symbols. One way to address this is to use the
generate_export_header() function on the shared library as normal, then make
the static library re-use the same export definitions. The key to making this
work is to ensure the static library has a compile definition for the name of
the shared library target with _EXPORTS appended, which is how the generated
header detects whether the code is being built as part of the shared library or
not.
add_library(MyShared SHARED shared.cpp)
add_library(MyStatic STATIC static.cpp)
include(GenerateExportHeader)
generate_export_header(MyShared BASE_NAME mine)
target_link_libraries(MyShared
PRIVATE MyStatic
)
target_include_directories(MyShared
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
)
target_include_directories(MyStatic
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
)
# This makes the static library code appear to be part of
# the shared library as far as the generated export header
# is concerned
target_compile_definitions(MyStatic
PRIVATE MyShared_EXPORTS
)add_library(MyShared SHARED shared.cpp)
add_library(MyStatic STATIC static.cpp)
include(GenerateExportHeader)
generate_export_header(MyShared BASE_NAME mine)
target_link_libraries(MyShared
PRIVATE MyStatic
)
target_include_directories(MyShared
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
)
target_include_directories(MyStatic
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
)
# This makes the static library code appear to be part of
# the shared library as far as the generated export header
# is concerned
target_compile_definitions(MyStatic
PRIVATE MyShared_EXPORTS
)
#include "mine_export.h"
MINE_EXPORT void sharedFunc();#include "mine_export.h"
MINE_EXPORT void sharedFunc();
#include "mine_export.h"
MINE_EXPORT void staticFunc();#include "mine_export.h"
MINE_EXPORT void staticFunc();
另一个需要考虑的因素是链接器在链接共享库时是否会丢弃静态库中定义的代码或数据。如果它确定没有任何东西正在使用特定符号,则链接器可能会丢弃它作为优化。可能需要采取特殊步骤来防止它这样做。一种选择是使共享库显式使用要从共享库中保留的每个符号。这样做的优点是它适用于所有编译器和链接器,但对于重要的项目可能不可行。替代方案本质上需要添加特定于链接器的标志,例如--whole-archiveUnix系统或 Visual Studio 上的链接器--no-whole-archive,
但此类功能可能不适用于所有链接器。如果确保共享库使用其静态库导出的每个符号不切实际,则可能值得考虑将这些静态库转为共享库。ld/WHOLEARCHIVE
The other factor to consider is whether the linker will discard code or data
defined in the static library when it comes to linking the shared library. If
it determines that nothing is using a particular symbol, the linker may discard
it as an optimization. Special steps may need to be taken to prevent it from
doing this. One choice is to make the shared library explicitly use every
symbol to be retained from the shared libraries. This has the advantage that it
would work for all compilers and linkers, but it may not be feasible for
non-trivial projects. The alternative essentially requires linker-specific
flags to be added, such as --whole-archive and --no-whole-archive for the
ld linker on Unix systems, or /WHOLEARCHIVE with Visual Studio, but such
functionality may not be available with all linkers. If ensuring the shared
library uses each symbol exported by its static libraries isn’t practical, it
may be worth considering turning those static libraries into shared instead.
如果共享库仅以私有方式链接到静态库(意味着不需要导出任何静态库的符号),那么情况会容易得多。在某些平台上,除了简单地将共享库链接到静态库之外,不需要采取进一步的操作。在其他情况下,可能会出现一两个小皱纹,需要解决。例如,在许多 64 位 Unix 系统上,如果代码要进入共享库,则必须将其编译为位置无关的,而静态库则没有这样的要求。但是,如果共享库链接到静态库,则必须将静态库构建为位置无关的。
If a shared library only links to static libraries in a private fashion (meaning none of the static libraries’ symbols need to be exported), then the situation is considerably easier. On some platforms, no further action is needed other than simply linking the shared library to the static libraries. On others, one or two minor wrinkles may arise which need to be addressed. On many 64-bit Unix systems, for example, code has to be compiled as position independent if it is to go into a shared library, whereas there is no such requirement for static libraries. If, however, a shared library links to a static library, then the static library does have to be built as position independent.
CMake 提供POSITION_INDEPENDENT_CODE目标属性,作为在需要它的平台上透明处理位置无关行为的一种方式。当设置为 true 时,这会导致该目标的代码被构建为位置无关的。默认情况下,该属性适用ON于库和SHARED所有
其他类型的目标。可以通过设置变量来覆盖默认值,在这种情况下,它将用于在创建目标时初始化目标属性。MODULEOFFCMAKE_POSITION_INDEPENDENT_CODEPOSITION_INDEPENDENT_CODE
CMake provides the POSITION_INDEPENDENT_CODE target property as a way of
transparently handling position independent behavior on those platforms that
require it. When set to true, this causes that target’s code to be built as
position independent. By default, the property is ON for SHARED and
MODULE libraries and OFF for all other types of targets. The default can be
overridden by setting the CMAKE_POSITION_INDEPENDENT_CODE variable, in
which case it will be used to initialize the POSITION_INDEPENDENT_CODE target
property when the target is created.
add_library(MyShared SHARED shared.cpp)
add_library(MyStatic STATIC static.cpp)
target_link_libraries(MyShared PRIVATE MyStatic)
set_target_properties(MyStatic PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
add_library(MyOtherStatic STATIC other.cpp)
target_link_libraries(MyShared PRIVATE MyOtherStatic)add_library(MyShared SHARED shared.cpp)
add_library(MyStatic STATIC static.cpp)
target_link_libraries(MyShared PRIVATE MyStatic)
set_target_properties(MyStatic PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
add_library(MyOtherStatic STATIC other.cpp)
target_link_libraries(MyShared PRIVATE MyOtherStatic)
使用MODULE用于按需加载可选插件的库和SHARED
用于链接的库。使用共享库,其中必须严格控制向库的使用者公开的符号,无论是出于 API 目的还是为了隐藏敏感的实现细节。如果旨在将库作为发布包的一部分提供,则在大多数情况下,共享库往往比静态库更受青睐。
Use MODULE libraries for optional plugins to be loaded on demand and SHARED
libraries for linking against. Use shared libraries where the symbols to be
exposed to consumers of the library must be tightly controlled, either for API
purposes or to hide sensitive implementation details. If aiming to deliver a
library as part of a release package, shared libraries tend to be preferred
over static libraries in most cases.
如果目标使用库中的某些内容,则它应该始终直接链接到该库。即使库已经是目标链接到的其他内容的链接依赖项,也不要依赖目标直接使用的内容的间接链接依赖项。如果其他目标更改了其实现并且不再链接到库,则主目标将不再构建。此外,表达正确的链接依赖类型;
PRIVATE,PUBLIC或者INTERFACE。这可确保 CMake 正确处理共享库和静态库的传递链接依赖项。以正确的可见性级别指定所有直接依赖项对于确保 CMake 使用正确的库排序构建可靠的链接器命令行至关重要。
If a target uses something from a library, it should always link directly to
that library. Even if the library is already a link dependency of something
else the target links to, do not rely on an indirect link dependency for
something a target uses directly. If that other target changes its
implementation and it no longer links against the library, the main target will
no longer build. Furthermore, express the right type of link dependency;
PRIVATE, PUBLIC or INTERFACE. This ensures CMake correctly handles
transitive link dependencies for both shared and static libraries. Specifying
all the direct dependencies with the correct level of visibility is essential
for ensuring CMake constructs a reliable linker command line with correct
library ordering.
使用正确的链接可见性还有一个额外的好处,即使用目标不必了解内部使用的所有不同的库依赖项,它们只需要链接到一个库并让该库定义自己的依赖项。然后,CMake 负责确保在最终链接器命令行上以正确的顺序指定所有必需的库。抵制简单地建立所有链接依赖关系的诱惑PUBLIC,因为这会将其他私有库的可见性扩展到可能不需要的地方。当打包项目进行发布或分发时,这一点变得尤为重要。
Using the correct link visibility has the added benefit that consuming targets
don’t have to know about all the different library dependencies used
internally, they only need to link to a library and let that library define its
own dependencies. CMake then takes care of ensuring all required libraries are
specified in the correct order on the final linker command line. Resist the
temptation to simply make all link dependencies PUBLIC, since this extends
the visibility of otherwise private libraries into places where it may be
undesirable. This becomes particularly important when packaging up a project
for release or distribution.
考虑尽早使用库版本控制策略。一旦库被发布到野外,版本号就具有一些关于二进制兼容性的非常具体的含义。使用
VERSION和SOVERSIONtarget 属性来指定库版本,即使最初这些属性在项目生命周期的早期被设置为一些基本占位符。在没有任何其他策略的情况下,一种合理的选择是从 0.1.0 开始版本编号,因为人们倾向于将 0.0.0 解释为默认值或错误地未设置版本,而 1.0.0 有时被视为意味着首次公开发布。认真考虑采用语义版本控制来处理此后的版本更改。另请记住,库版本的更改可能会对发布过程、打包等产生令人惊讶的强烈影响,并且开发人员需要时间在公开发布这些库之前提前了解共享库的版本号的含义。还要考虑项目版本和库版本之间是否应该有任何关系。一旦发布了第一个版本,就很难改变这种关系,因此要小心链接它们,除非它们具有很强的关联性(一个以 SDK 形式提供一组连贯的库的项目就是一个强关联性的例子) )。
Consider using a library versioning strategy as early as possible. Once a
library has been released into the wild, the version number has some very
specific meanings with regard to binary compatibility. Make use of the
VERSION and SOVERSION target properties to specify the library version,
even if initially these are set to some basic placeholders early in the life of
the project. In the absence of any other strategy, one reasonable option is to
start version numbering at 0.1.0, since people tend to interpret 0.0.0 as a
default value or the version mistakenly not having been set, while 1.0.0 is
sometimes taken to imply the first public release. Give strong consideration to
adopting semantic versioning for handling version changes thereafter. Also keep
in mind that changes in library versions can have a surprisingly strong
influence on things like release processes, packaging, etc. and developers need
time to learn the implications of version numbers for shared libraries well in
advance of those libraries being released publicly. Consider also whether the
project version and library version should have any relationship to each other
or not. It can be very difficult to change such a relationship once the first
release is made, so be wary of linking them unless they have a strong
association (a project delivering a coherent set of libraries as a SDK would be
one such example of a strong association).
如果有特定的支持工具包、库等可用,某些项目可以选择提供某些功能。为了允许构建的其他部分或实际上其他消费项目检测或检查与该可选功能或特性的一致性,可以提供接口兼容性详细信息。考虑相关功能是否需要具有库之外的可见性,例如允许使用目标检测该功能是否受支持或确认所选实现是否提供所需的所有功能。还要考虑指定和使用接口兼容性所增加的复杂性是否会带来足够的好处,使其值得,因为库依赖关系层次结构越深,有效使用接口兼容性就越困难。
Some projects can optionally provide certain functionality if a particular supporting toolkit, library, etc. is available. To allow other parts of the build or indeed other consuming projects to detect or check consistency with that optional functionality or feature, interface compatibility details can be provided. Consider whether the feature in question needs to have visibility beyond the library, such as allowing consuming targets to detect whether or not the feature is supported or confirming whether the selected implementation provides all the capabilities required. Also consider whether the added complexity of specifying and using interface compatibilities brings with it sufficient benefits to make it worthwhile, as the deeper the library dependency hierarchy becomes, the harder it can be to use interface compatibilities effectively.
在项目生命周期的早期就考虑符号可见性,因为稍后返回并使用符号可见性详细信息改造项目可能非常困难。创建库时,要养成始终思考特定类、函数或变量是否应该可供库外部的任何内容访问的心态。认为任何具有外部可见性的东西都很难改变,而内部的东西可以根据需要在版本之间更自由地修改。使用隐藏可见性作为默认值,并明确标记要导出的每个单独实体,最好使用函数提供的宏,generate_export_header()以便 CMake 代表项目处理各种平台差异。还可以考虑使用该函数提供的弃用宏来清楚地识别库 API 中已弃用的部分以及可能在未来版本中删除的部分。
Give consideration to symbol visibility as early in the life of a project as
possible, as it can be very difficult to go back and retrofit a project with
symbol visibility details later. When creating libraries, develop the mindset
of always thinking about whether a particular class, function or variable
should be accessible to anything outside of the library. Think of anything that
has external visibility as being very hard to change, whereas internal things
can be more freely modified between releases as needed. Use hidden visibility
as the default and explicitly mark each individual entity to be exported,
ideally with macros provided by the generate_export_header() function so that
CMake handles the various platform differences on the project’s behalf. Also
consider using the deprecation macros provided by that function to clearly
identify those parts of a library’s API that have been deprecated and which may
be removed in a future version.
混合共享库和静态库时要格外小心。在可能的情况下,优先使用其中之一而不是同时使用两者,因为这可以避免与构建设置一致性和符号可见性控制相关的一些困难。在混合两种库类型有意义的情况下,请尝试确保静态库仅链接到一个共享库,并且没有其他目标链接到这些静态库。将静态库视为共享库中的子组,外部目标仅链接到共享库。更好的是,考虑直接将代码从静态库提取到共享库中,完全摆脱静态库。第 29.5.1 节“目标源”中介绍的技术 演示了如何逐步将源添加到现有目标,从而允许目标源方便地跨子目录累积。
Take extra care when mixing shared and static libraries. Where possible, prefer to use one or the other rather than both, as this avoids some of the difficulties associated with build setting consistency and symbol visibility control. Where it makes sense to mix both library types, try to ensure that static libraries only get linked into one shared library and no other targets link to those static libraries. Treat the static libraries as being sub-groups within the shared library, with outside targets only ever linking to the shared library. Even better though, consider pulling the code up from the static libraries into the shared library directly instead, getting rid of the static libraries altogether. The techniques presented in Section 29.5.1, “Target Sources” demonstrate how to add sources to an existing target progressively, allowing the target sources to be conveniently accumulated across subdirectories.
在考虑构建软件的过程和所涉及的工具时,开发人员通常会考虑编译器和链接器。虽然这些是开发人员接触到的主要工具,但还有许多其他工具、库和支持文件也有助于该过程。宽松地说,这套更广泛的工具和其他文件统称为工具链。
When considering the process of building software and the tools involved, developers typically think about the compiler and linker. While these are the primary tools that developers are exposed to, there are a number of other tools, libraries and supporting files that also contribute to the process. Loosely speaking, this broader set of tools and other files is collectively referred to as the toolchain.
对于桌面或传统服务器应用程序,通常不需要太深入地考虑工具链。在大多数情况下,决定使用主流平台工具链的哪个版本非常复杂。CMake 通常不需要太多帮助就能找到工具链,开发人员可以继续编写软件的任务。然而,对于移动或嵌入式开发,情况就完全不同了。工具链通常需要由开发人员以某种方式指定。这可以像指定不同的目标系统名称一样简单,也可以像指定各个工具和目标根文件系统的路径一样复杂。可能还需要设置特殊标志以使工具生成支持正确芯片组、具有所需性能特征等的二进制文件。
For desktop or traditional server applications, there usually isn’t a great need to think too deeply about the toolchain. In most cases, deciding which release of the prevailing platform toolchain to use is about as complicated as it gets. CMake usually finds the toolchain without needing much help and the developer can get on with the task of writing software. For mobile or embedded development, however, the situation is quite different. The toolchain will normally need to be specified in some way by the developer. This can be as simple as specifying a different target system name, or it can be as complex as specifying the paths to individual tools and a target root file system. Special flags may also need to be set to make the tools produce binaries that will support the right chipset, have the required performance characteristics and so on.
选择工具链后,CMake 会在内部执行大量处理来测试工具链,以确定其支持的功能、设置各种属性和变量等。即使对于使用默认工具链的传统构建也是如此,不仅仅适用于交叉编译的构建。这些测试的结果可以在 CMake 第一次针对给定构建目录运行时的输出中看到。GCC 的示例可能如下所示:
Once a toolchain has been selected, CMake performs quite a bit of processing internally to test the toolchain to determine the features it supports, set various properties and variables, etc. This is the case even for a traditional build where the default toolchain is used, not just for builds that are cross-compiling. The results of these tests can be seen in CMake’s output the first time it is run for a given build directory. An example for GCC may look something like this:
-- C编译器标识为GNU 9.3.0 -- CXX编译器标识为GNU 9.3.0 -- 检测C编译器ABI信息 -- 检测 C 编译器 ABI 信息 - 完成 -- 检查正在运行的 C 编译器:/usr/bin/cc - 已跳过 -- 检测C编译特性 -- 检测 C 编译功能 - 完成 -- 检测CXX编译器ABI信息 -- 检测 CXX 编译器 ABI 信息 - 完成 -- 检查正在运行的 CXX 编译器:/usr/bin/c++ - 已跳过 -- 检测CXX编译特性 -- 检测 CXX 编译功能 - 完成
-- The C compiler identification is GNU 9.3.0 -- The CXX compiler identification is GNU 9.3.0 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: /usr/bin/cc - skipped -- Detecting C compile features -- Detecting C compile features - done -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done
大部分处理通常发生在
project()调用第一个命令时,然后缓存工具链测试的结果。当该enable_language()命令启用以前未启用的语言时,也会触发此类处理,就像project()
添加以前未启用的语言的另一个调用一样。一旦启用某种语言,即使对于后续的 CMake 运行,也将始终使用其缓存的详细信息,而不是重新测试工具链。这至少有两个重要的后果:
The bulk of this processing usually occurs at the point where the first
project() command is called and the results of the toolchain tests are
then cached. The enable_language() command also triggers such processing
when it enables a previously non-enabled language, as would another project()
call that adds a previously non-enabled language. Once a language has been
enabled, its cached details will always be used rather than re-testing the
toolchain, even for subsequent CMake runs. This has at least two important
consequences:
CMakeCache.txt,其他详细信息可能缓存在不同的位置)。
CMakeCache.txt file, other details may be cached in
different locations).
如果默认工具链不合适,则指定所需工具链详细信息的推荐方法是使用工具链文件。这只是一个普通的 CMake 脚本,通常包含主要set(…)
命令。这些将定义 CMake 用于描述目标平台、各种工具链组件的位置等的变量。工具链文件的名称通过特殊的缓存变量传递给 CMake,CMAKE_TOOLCHAIN_FILE如下所示:
If the default toolchain is not suitable, then the recommended way of
specifying the desired toolchain details is with a toolchain file. This is
just an ordinary CMake script which typically contains mostly set(…)
commands. These would define the variables that CMake uses to describe the
target platform, the location of the various toolchain components and so on.
The name of the toolchain file is passed to CMake through the special cache
variable CMAKE_TOOLCHAIN_FILE like so:
cmake -DCMAKE_TOOLCHAIN_FILE=myToolchain.cmake 路径/to/src
cmake -DCMAKE_TOOLCHAIN_FILE=myToolchain.cmake path/to/src
CMake 3.21 或更高版本还支持--toolchain命令行选项或回退到CMAKE_TOOLCHAIN_FILE环境变量,但结果最终是相同的(即CMAKE_TOOLCHAIN_FILE设置了缓存变量)。
CMake 3.21 or later also supports a --toolchain command-line option or a
fallback to a CMAKE_TOOLCHAIN_FILE environment variable, but the result is
ultimately the same (i.e. the CMAKE_TOOLCHAIN_FILE cache variable is set).
cmake --toolchain myToolchain.cmake 路径/to/src
cmake --toolchain myToolchain.cmake path/to/src
# 为当前 shell 设置一次 导出 CMAKE_TOOLCHAIN_FILE=myToolchain.cmake # 未指定工具链,使用环境变量 cmake 路径/到/源
# Set once for the current shell export CMAKE_TOOLCHAIN_FILE=myToolchain.cmake # No toolchain specified, uses the environment variable cmake path/to/source
可以使用完整的绝对路径,或者对于上面示例中的相对路径,CMake 首先相对于构建目录的顶部进行查找,如果在那里找不到,则相对于源目录的顶部进行查找。该工具链文件必须在第一次为构建目录运行 CMake 时指定,以后不能添加或更改为指向不同的工具链。由于变量本身已被缓存,因此无需为任何后续 CMake 运行再次重新指定它。
A full absolute path can be used, or for a relative path like in the above examples, CMake first looks relative to the top of the build directory, then if not found there, relative to the top of the source directory. This toolchain file must be specified the first time CMake is run for the build directory, it cannot be added later or changed to point to a different toolchain. Since the variable itself is cached, there is no need to respecify it again for any subsequent CMake runs.
第一次调用命令时会读取工具链文件project(),可能会多次读取。后续运行中读取的次数也可能与第一次运行不同。工具链文件也可以由 CMake 在内部设置的临时子项目读取,以在首次启用语言时测试工具链。考虑到这些因素,工具链文件应该支持多次包含,并且不应该包含假设它仅由主项目读取的逻辑。
The toolchain file is read by the first call to the project() command,
possibly more than once.
The number of times it is read in subsequent runs may also be different to the
first run.
The toolchain file may also be read by temporary sub-projects that CMake sets
up internally to test the toolchain when a language is enabled for the first
time.
Given these factors, a toolchain file should support being included multiple
times and it should not contain logic that assumes it is being read only by the
main project.
开发人员应该致力于使工具链文件最少,仅设置所需的内容,并对项目的功能做出尽可能少的假设。理想情况下,工具链文件应该与项目完全解耦,甚至应该可以在不同的项目之间重用,因为它们应该只描述工具链,而不是它们如何与特定项目交互。
Developers should aim to make toolchain files minimal, setting only the things needed and making as few assumptions about what the project does as possible. Toolchain files should ideally be completely decoupled from the project and should even be reusable across different projects, since they should only be describing the toolchain, not how they interact with a particular project.
工具链文件的内容可能会有所不同,但总的来说,它们可能需要做的主要事情只有几件:
The contents of a toolchain file can vary, but on the whole there are only a few main things they potentially need to do:
工具链文件中还包含其他逻辑,特别是影响各种find_…()命令的行为(请参阅第 24 章,查找事物),这是很常见的。虽然在某些情况下这种逻辑可能是合适的,但人们可以提出这样的论点:在大多数情况下,这种逻辑可以而且应该成为项目的一部分。只有项目知道它试图找到什么,因此工具链不应该对项目想要做什么做出假设。
It is quite common to see other logic included in toolchain files as well,
especially for influencing the behavior of the various find_…() commands
(see Chapter 24, Finding Things). While there are situations where such logic may be
appropriate, one can mount an argument that such logic can and should be part
of the project instead in most cases. Only the project knows what it is trying
to find, so the toolchain should not make assumptions about what the project
wants to do.
描述目标系统的基本变量是:
The fundamental variables that describe the target system are:
CMAKE_SYSTEM_NAME
CMAKE_SYSTEM_NAME
CMAKE_SYSTEM_PROCESSOR
CMAKE_SYSTEM_PROCESSOR
CMAKE_SYSTEM_VERSION
CMAKE_SYSTEM_VERSION
其中,CMAKE_SYSTEM_NAME最为重要的是。它定义了目标平台的类型,而不是定义正在执行CMAKE_HOST_SYSTEM_NAME构建的平台。CMake 本身始终设置,但可以(并且通常是)由工具链文件设置。我们可以将其视为如果 CMake 直接在目标平台上运行的话将会设置的值。因此,典型值包括、
、或,但对于某些情况(例如裸机嵌入式设备),可以使用系统名称 来代替。典型平台名称也有一些变化,这些名称可能适用于某些情况,例如和。如果
在工具链文件中设置,则 CMake 也会将该
变量设置为 true,即使它与 具有相同的值
。如果未设置,它将被赋予与自动检测到的相同的值。CMAKE_HOST_SYSTEM_NAMECMAKE_SYSTEM_NAMECMAKE_SYSTEM_NAMECMAKE_HOST_SYSTEM_NAMELinuxWindowsQNXAndroidDarwinGenericWindowsStoreWindowsPhoneCMAKE_SYSTEM_NAMECMAKE_CROSSCOMPILINGCMAKE_HOST_SYSTEM_NAMECMAKE_SYSTEM_NAMECMAKE_HOST_SYSTEM_NAME
Of these, CMAKE_SYSTEM_NAME is the most important. It defines the type of
platform being targeted, as opposed to CMAKE_HOST_SYSTEM_NAME which
defines the platform on which the build is being performed. CMake itself
always sets CMAKE_HOST_SYSTEM_NAME, but CMAKE_SYSTEM_NAME can be (and
often is) set by toolchain files. One can think of CMAKE_SYSTEM_NAME as being
what CMAKE_HOST_SYSTEM_NAME would be set to if CMake was run
directly on the target platform. Thus, typical values include Linux,
Windows, QNX, Android or Darwin, but for certain situations (e.g. bare
metal embedded devices), a system name of Generic may be used instead. There
are also variations on the typical platform names which can be appropriate in
some situations, such as WindowsStore and WindowsPhone. If
CMAKE_SYSTEM_NAME is set in a toolchain file, then CMake will also set the
CMAKE_CROSSCOMPILING variable to true, even if it has the same value as
CMAKE_HOST_SYSTEM_NAME. If CMAKE_SYSTEM_NAME is not set, it will be given
the same value as the auto-detected CMAKE_HOST_SYSTEM_NAME.
CMAKE_SYSTEM_PROCESSOR旨在描述目标平台的硬件架构。如果未指定,它将被赋予与 相同的值
CMAKE_HOST_SYSTEM_PROCESSOR,该值由 CMake 自动填充。在交叉编译场景中或在相同系统类型的 64 位主机上构建 32 位平台时,这将导致CMAKE_SYSTEM_PROCESSOR
不正确。CMAKE_SYSTEM_PROCESSOR因此,如果架构与构建主机不匹配,建议进行设置,即使项目在没有它的情况下似乎构建正常。基于不正确
CMAKE_SYSTEM_PROCESSOR值的错误决策可能会导致难以检测或诊断的微妙问题。
CMAKE_SYSTEM_PROCESSOR is intended to describe the hardware architecture of
the target platform. If not specified, it will be given the same value as
CMAKE_HOST_SYSTEM_PROCESSOR, which is automatically populated by CMake.
In cross-compiling scenarios or when building for a 32-bit platform on a 64-bit
host of the same system type, this will result in CMAKE_SYSTEM_PROCESSOR
being incorrect. Therefore, it is advisable to set CMAKE_SYSTEM_PROCESSOR if
the architecture doesn’t match the build host, even if the project seems to
build okay without it. Wrong decisions based on an incorrect
CMAKE_SYSTEM_PROCESSOR value can lead to subtle problems that may not be
easy to detect or diagnose.
CMAKE_SYSTEM_VERSION根据
CMAKE_SYSTEM_NAME设置的不同,该变量具有不同的含义。例如,如果系统名称为
WindowsStore、WindowsPhone或WindowsCE,系统版本将用于定义要使用的 Windows SDK。值可能更通用,例如 8.1 或 10.0,也可能定义非常具体的版本,例如 10.0.10240.0。作为另一个示例,如果CMAKE_SYSTEM_NAME设置为Android,则
CMAKE_SYSTEM_VERSION通常会被解释为默认 Android API 版本,并且必须是正整数。CMAKE_SYSTEM_VERSION对于其他系统名称,设置为任意值(例如 1)或根本不设置的情况并不罕见。CMake 文档的工具链部分提供了 的不同用法的示例CMAKE_SYSTEM_VERSION,但变量的含义和允许值集并不总是明确定义。因此,建议项目在实现依赖于 的值的逻辑时要小心谨慎CMAKE_SYSTEM_VERSION。
The CMAKE_SYSTEM_VERSION variable has different meanings depending on what
CMAKE_SYSTEM_NAME is set to. For example, with a system name of
WindowsStore, WindowsPhone or WindowsCE, the system version will be used
to define which Windows SDK to use. Values might be more general like 8.1 or
10.0, or they might define a very specific release, such as 10.0.10240.0. As
another example, if CMAKE_SYSTEM_NAME is set to Android, then
CMAKE_SYSTEM_VERSION will typically be interpreted as the default Android API
version and must be a positive integer. For other system names, it is not
unusual to see CMAKE_SYSTEM_VERSION set to something arbitrary like 1, or to
not be set at all. The toolchains section of the CMake documentation provides
examples of different uses of CMAKE_SYSTEM_VERSION, but the meaning and the
set of allowable values for the variable are not always clearly defined. For
this reason, projects are advised to exercise caution if implementing logic
that depends on the value of CMAKE_SYSTEM_VERSION.
通常,这三个CMAKE_SYSTEM_…变量完整地描述了目标系统,但也有例外:
Normally, these three CMAKE_SYSTEM_… variables fully describe the target
system, but there are exceptions:
Darwin,
CMAKE_SYSTEM_NAME甚至适用于 iOS、tvOS 或 watchOS。然后由变量选择实际的目标系统CMAKE_OSX_SYSROOT,该变量选择用于构建的基础 SDK。目标设备是根据所选的 SDK 确定的,但开发人员仍然可以在构建时在设备或模拟器之间进行选择。这是一个复杂的主题,第 23.5 节“构建设置”中有详细介绍。对专用CMAKE_SYSTEM_NAME值的支持iOS,在 CMake 3.14 中tvOS添加
watchOS,以更好地区分不同平台,并使它们与其他平台的处理方式更加一致。
Darwin for the
CMAKE_SYSTEM_NAME, even for iOS, tvOS or watchOS. The actual target system
is then selected by the CMAKE_OSX_SYSROOT variable, which selects the base
SDK to be used for the build. The target device is determined based on the SDK
chosen, but the developer can still choose between device or simulator at build
time. This is a complex topic and is covered in detail in Section 23.5, “Build Settings”.
Support for the dedicated CMAKE_SYSTEM_NAME values iOS, tvOS and
watchOS was added in CMake 3.14 to better distinguish the different platforms
and make them more consistent with how other platforms are handled.
CMAKE_SYSTEM_PROCESSOR对于 Apple 平台来说并不是CMAKE_SYSTEM_VERSION特别有意义,通常保持未设置状态。
CMAKE_SYSTEM_PROCESSOR and CMAKE_SYSTEM_VERSION are not particularly
meaningful for Apple platforms and usually remain unset.
CMAKE_SYSTEM_PROCESSOR当针对 Android 平台时,通常不会设置该变量。这将在下面第 22.6.3 节“Android”中进一步讨论。
CMAKE_SYSTEM_PROCESSOR variable is typically not set when targeting
Android platforms. This is discussed further in Section 22.6.3, “Android” below.
此外,一些项目生成器支持自己的本机平台名称。CMAKE_SYSTEM_NAME对于此类生成器,可以通过几种不同的方法指定本机平台名称,而不是设置变量。cmake最简单、最直接的方法是使用选项在命令行上指定本机平台以及生成器详细信息-A。例如,可以指示 Visual Studio 生成器以
x64平台为目标,如下所示:
Furthermore, some project generators support their own native platform names.
For such generators, instead of setting the CMAKE_SYSTEM_NAME variable, a
native platform name can be specified via a few different methods.
The simplest and most direct is to specify the native platform along with the
generator details on the cmake command line using the -A option.
For example, the Visual Studio generator can be instructed to target the
x64 platform like so:
cmake -G“Visual Studio 2019”-A x64
cmake -G "Visual Studio 2019" -A x64
所选平台将通过 CMake 变量可供项目使用
CMAKE_GENERATOR_PLATFORM。或者,开发人员可以选择使用工具链文件并
CMAKE_GENERATOR_PLATFORM直接设置 CMake 变量(项目不应自行设置此 CMake 变量)。如果使用 CMake 3.15 或更高版本,另一种选择是通过环境变量提供平台
CMAKE_GENERATOR_PLATFORM。
The chosen platform will be available to projects through the
CMAKE_GENERATOR_PLATFORM CMake variable.
Alternatively, developers may choose to use a toolchain file and to set the
CMAKE_GENERATOR_PLATFORM CMake variable directly (projects should never set
this CMake variable themselves).
If using CMake 3.15 or later, another choice is to provide the platform via the
CMAKE_GENERATOR_PLATFORM environment variable instead.
从开发人员的角度来看,在构建中使用的所有工具中,编译器可能是最重要的。编译器的路径由
CMAKE_<LANG>_COMPILER变量控制,可以在工具链文件或命令行中设置该变量来手动控制所使用的编译器,也可以省略该变量以允许 CMake 自动选择一个。如果手动提供可执行文件的名称而不包含路径,CMake 将使用(第 24.3 节“查找程序”find_program()中介绍)来搜索它
。如果提供了编译器的完整路径,则将直接使用它。如果没有手动指定编译器,CMake 将根据目标平台和生成器的内部默认值集选择编译器。
Of all the tools used in the build, the compiler is probably the most important
from the developer’s perspective. The path to the compiler is controlled by the
CMAKE_<LANG>_COMPILER variable, which can be set in a toolchain file or
on the command line to manually control the compiler used, or it can be omitted
to allow CMake to choose one automatically. If the name of an executable is
provided manually without a path, CMake will search for it using
find_program() (covered in Section 24.3, “Finding Programs”). If a full path to a
compiler is provided, it will be used directly. If no compiler is manually
specified, CMake will select a compiler based on an internal set of defaults
for the target platform and generator.
从 CMake 3.19 开始,CMAKE_<LANG>_COMPILER可以是一个列表。列表中的第一项是要使用的编译器,如上所述。其余列表项是编译器必须存在的编译器选项才能正常工作。不要通过此变量添加非强制选项。另请注意,CMAKE_<LANG>_COMPILER在第一次 CMake 运行后不应更改。
From CMake 3.19, CMAKE_<LANG>_COMPILER can be a list.
The first item in the list is the compiler to use, just as described above.
The remaining list items are compiler options that have to be present for the
compiler to work.
Do not add non-mandatory options via this variable.
Note also that CMAKE_<LANG>_COMPILER should not be changed after the first
CMake run.
大多数语言还支持通过指定环境变量来设置编译器,而不必设置CMAKE_<LANG>_COMPILER. 这些通常遵循通用约定,例如CCC 编译器、CXXC++ 编译器、FCFortran 编译器等。CMAKE_<LANG>_COMPILER这些环境变量仅在第一次在构建目录中运行 CMake 时并且仅当相应的变量未由工具链文件或 CMake 命令行设置时才会起作用。
Most languages also have support for setting the compiler by specifying an
environment variable instead of having to set CMAKE_<LANG>_COMPILER. These
usually follow common conventions, such as CC for a C compiler, CXX for a
C++ compiler, FC for a Fortran compiler and so on. These environment
variables will only have an effect the first time CMake is run in a build
directory and only if the corresponding CMAKE_<LANG>_COMPILER variable is not
set by a toolchain file or on the CMake command line.
一些生成器支持自己单独的工具集规范,其工作方式与上述方法不同。可以使用命令行-T上的选项来选择这些工具集cmake,或者如果使用 CMake 3.15 或更高版本,则可以通过设置CMAKE_GENERATOR_TOOLSET
环境变量来选择。还可以通过设置 CMake 变量在工具链文件中选择它们
CMAKE_GENERATOR_TOOLSET(项目不应自行设置此变量)。可用的工具集和支持的语法特定于每个生成器,但以下示例演示了一些可能性。
Some generators support their own separate toolset specifications which work
differently to the above methods.
These toolsets can be selected using the -T option on the cmake command
line, or if using CMake 3.15 or later, by setting the CMAKE_GENERATOR_TOOLSET
environment variable.
They can also be selected in a toolchain file by setting the
CMAKE_GENERATOR_TOOLSET CMake variable (projects should never set this
variable themselves).
The available toolsets and the supported syntax are specific to each generator,
but the following examples demonstrate some of the possibilities.
cmake -G“Visual Studio 2019”-A Win32 -T 主机=x64 ...
cmake -G "Visual Studio 2019" -A Win32 -T host=x64 ...
cmake -G“Visual Studio 2019”-T llvm ...
cmake -G "Visual Studio 2019" -T llvm ...
对于某些生成器,安装构建工具的多个实例可能意味着开发人员需要指定要使用哪个实例。一个典型的例子是开发人员尝试 Visual Studio 的预览版本,然后在不删除预览版本的情况下安装发行版本。使用 CMake 3.11 或更高版本时,CMAKE_GENERATOR_INSTANCE可以在工具链文件中设置变量来控制将使用的特定实例。使用 CMake 3.15 或更高版本,CMAKE_GENERATOR_INSTANCE可以改为设置环境变量。很少有生成器支持此功能,目前仅支持 Visual Studio 2017 或更高版本的生成器。
For some generators, having multiple instances of the build tool installed may
mean the developer needs to specify which instance to use.
A typical example is a developer trying out a preview version of Visual Studio
and then later installing the release version without deleting the preview
version.
When using CMake 3.11 or later, the CMAKE_GENERATOR_INSTANCE variable may
be set in a toolchain file to control the specific instance that will be used.
With CMake 3.15 or later, the CMAKE_GENERATOR_INSTANCE environment variable
can be set instead.
Few generators support this feature, currently only those for Visual Studio
2017 or later.
指定工具链后,CMake 将识别编译器并尝试确定其版本。CMAKE_<LANG>_COMPILER_ID该编译器信息将分别通过和变量提供
CMAKE_<LANG>_COMPILER_VERSION
。编译器ID
是一个短字符串,用于区分一个编译器与另一个编译器,常见值为GNU、Clang、AppleClang和。CMake 文档提供了支持的 ID 的完整列表。如果可以确定编译器版本,它将具有通常的
major.minor.patch.tweak形式,其中不需要存在所有版本组件(例如4.9 将是有效版本)。MSVCIntelCMAKE_<LANG>_COMPILER_ID
With the toolchain specified, CMake will identify the compiler and try to
determine its version.
This compiler information will be made available through the
CMAKE_<LANG>_COMPILER_ID and CMAKE_<LANG>_COMPILER_VERSION
variables respectively.
The compiler ID is a short string used to differentiate one compiler from
another, with common values being GNU, Clang, AppleClang, MSVC and
Intel.
The CMake documentation for CMAKE_<LANG>_COMPILER_ID gives the full list of
supported IDs.
If the compiler version can be determined, it will have the usual
major.minor.patch.tweak form, where not all version components need to be
present (e.g. 4.9 would be a valid version).
除了CMAKE_<LANG>_COMPILER_ID和
变量之外,还支持CMAKE_<LANG>_COMPILER_VERSION不带前导部分的类似生成器表达式。CMAKE_变量或生成器表达式可用于仅针对某些编译器或编译器版本有条件地添加内容。例如,GCC 7 引入了一个新
-fcode-hoisting选项,以下显示了仅在可用时将其添加到 C++ 编译的两种方法:
In addition to the CMAKE_<LANG>_COMPILER_ID and
CMAKE_<LANG>_COMPILER_VERSION variables, analogous generator expressions
without the leading CMAKE_ part are also supported. Either the variables or
the generator expressions can be used to conditionally add content only for
certain compilers or compiler versions. For example, GCC 7 introduced a new
-fcode-hoisting option and the following shows both ways of adding it
for C++ compilation only if it is available:
# Conditionally add -fcode-hoisting option using variables
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 7)
target_compile_options(SomeTarget PRIVATE
-fcode-hoisting
)
endif()
# Same thing using generator expressions instead
set(isGNU $<CXX_COMPILER_ID:GNU>)
set(newEnough
$<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,7>
)
target_compile_options(SomeTarget PRIVATE
$<$<AND:${isGNU},${newEnough}>:-fcode-hoisting>
)# Conditionally add -fcode-hoisting option using variables
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 7)
target_compile_options(SomeTarget PRIVATE
-fcode-hoisting
)
endif()
# Same thing using generator expressions instead
set(isGNU $<CXX_COMPILER_ID:GNU>)
set(newEnough
$<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,7>
)
target_compile_options(SomeTarget PRIVATE
$<$<AND:${isGNU},${newEnough}>:-fcode-hoisting>
)
编译器 ID 是识别所用编译器的最可靠方法。项目可能需要注意的一种情况是,在 CMake 3.0 之前,Apple Clang 编译器被视为与上游 Clang 相同,并且两者都具有编译器 ID Clang。从 CMake 3.0 开始,Apple 的编译器改为使用编译器 ID AppleClang,以便与上游 Clang 区分开来。添加策略CMP0025是为了允许旧行为用于需要它的项目。
The compiler ID is the most robust way to identify the compiler used. The one
case projects may need to be aware of is that prior to CMake 3.0, the Apple
Clang compiler was treated the same as the upstream Clang and both had the
compiler ID Clang. From CMake 3.0 onward, Apple’s compiler has the compiler
ID AppleClang instead so that it can be differentiated from upstream Clang.
Policy CMP0025 was added to allow the old behavior to be used for those
projects that require it.
一旦确定了编译器的路径,CMake 就能够为编译器和链接器计算出适当的默认标志集。CMAKE_<LANG>_FLAGS这些可以通过变量、
CMAKE_<LANG>_FLAGS_<CONFIG>和CMAKE_<TARGETTYPE>_LINKER_FLAGS供
项目使用,这些变量在第 15.4 节“编译器和链接器变量”CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG>中进行了介绍
。开发人员可以使用同名但附加的变量将自己的标志添加到默认值集中。这些变量仅用于设置初始默认值,一旦 CMake 运行一次并且实际值已保存在缓存中,它们就不起作用。_INIT…_INIT
Once the path to the compiler has been determined, CMake is able to work out
the appropriate set of default flags for the compiler and linker. These are
made available to the project through the variables CMAKE_<LANG>_FLAGS,
CMAKE_<LANG>_FLAGS_<CONFIG>, CMAKE_<TARGETTYPE>_LINKER_FLAGS and
CMAKE_<TARGETTYPE>_LINKER_FLAGS_<CONFIG>, which were covered back in
Section 15.4, “Compiler And Linker Variables”. Developers can add their own flags
into the set of default values for these using variables of the same name but
with _INIT appended. These …_INIT variables are only ever used to set the
initial defaults, they have no effect once CMake has been run once and the
actual values have been saved in the cache.
…INIT一个常见的错误是在工具链文件中设置非变量(即设置CMAKE_<LANG>_FLAGS而不是CMAKE_<LANG>_FLAGS_INIT)。这会产生不良影响,即丢弃或隐藏开发人员可能对缓存中的这些变量所做的任何更改。project()工具链文件也可以作为稍后或
调用的一部分重新读取enable_language(),从而丢弃项目本身对这些变量所做的任何更改。相反,设置…INIT变量可确保仅初始默认值受到影响,并且…_INIT保留通过任何方法对非变量进行的任何后续更改。
A common mistake is to set the non-…INIT variables in a toolchain file
(i.e. setting CMAKE_<LANG>_FLAGS rather than CMAKE_<LANG>_FLAGS_INIT).
This has the undesirable effect of discarding or hiding any changes the
developer might make to these variables in the cache.
The toolchain file may also be re-read as part of later project() or
enable_language() calls, thereby discarding any changes to these variables
made by the project itself.
Setting the …INIT variables instead ensures that only the initial default
values are affected and any subsequent changes to the non-…_INIT variables
via any method are retained.
例如,考虑开发人员可能使用的工具链文件,使用特殊的编译器标志来设置其构建以进行调试(这可能是跨多个项目重复使用某些复杂的开发人员专用逻辑的有用方法,而无需将其添加到每个项目中)项目)。以下选择 GNU 编译器并添加启用大多数警告的标志:
As an example, consider a toolchain file a developer might use to set up their build with special compiler flags for debugging (this can be a useful way of re-using some complex developer-only logic across multiple projects without having to add it to each project). The following chooses GNU compilers and adds flags that enable most warnings:
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)
set(extraOpts "-Wall -Wextra")
set(CMAKE_C_FLAGS_DEBUG_INIT ${extraOpts})
set(CMAKE_CXX_FLAGS_DEBUG_INIT ${extraOpts})set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)
set(extraOpts "-Wall -Wextra")
set(CMAKE_C_FLAGS_DEBUG_INIT ${extraOpts})
set(CMAKE_CXX_FLAGS_DEBUG_INIT ${extraOpts})
不幸的是,CMake 将开发人员指定的选项与其通常提供的默认选项结合起来的方式存在一些不一致…_INIT。在大多数情况下,CMake 会将更多选项附加到变量指定的选项
…INIT,但对于某些平台/编译器组合(特别是较旧的或不常用的组合),…_INIT可以丢弃开发人员指定的值。这源于这些变量的历史,这些变量过去仅供内部使用,并且总是单方面设置值…_INIT。从 CMake 3.7 开始,…_INIT变量被记录为一般用途,并且行为已切换为附加而不是替换常用的编译器。非常旧的或不再积极维护的编译器的行为保持不变。
Unfortunately, there are some inconsistencies in how CMake combines
developer-specified …_INIT options with the defaults it normally provides.
In most cases, CMake will append further options to those specified by
…INIT variables, but with some platform/compiler combinations (particularly
older or less frequently used ones), developer-specified …_INIT values can
be discarded. This stems from the history of these variables, which used to be
for internal use only and always unilaterally set the …_INIT values. From
CMake 3.7, the …_INIT variables were documented for general use and the
behavior was switched to appending rather than replacing for the commonly used
compilers. The behavior for very old or no longer actively maintained compilers
was left unmodified.
一些编译器更多地充当编译器驱动程序,并期望命令行参数指定要编译的平台/体系结构(Clang 和 QNX qcc 是两个这样的示例)。对于 CMake 识别为需要此类参数的编译器,
CMAKE_<LANG>_COMPILER_TARGET可以在工具链文件中设置该变量以指定目标。在支持的情况下,应该使用它,而不是尝试手动添加带有CMAKE_<LANG>_FLAGS_INIT.
Some compilers act more as compiler drivers and expect a command line
argument specifying the platform/architecture to compile for (Clang and QNX qcc
are two such examples).
For compilers that CMake recognizes as requiring such arguments, the
CMAKE_<LANG>_COMPILER_TARGET variable can be set in a toolchain file to
specify the target. Where supported, this should be used instead of trying to
manually add the flags with CMAKE_<LANG>_FLAGS_INIT.
另一种不太常见的情况是编译器工具链不包含其他支持实用程序,例如归档器或链接器。这些编译器驱动程序通常支持命令行参数,该参数可用于指定在哪里可以找到这些工具。CMake 提供了
CMAKE_<LANG>_COMPILER_EXTERNAL_TOOLCHAIN可用于指定这些实用程序所在目录的变量。
Another less common situation is where the compiler toolchain does not include
other supporting utilities like archivers or linkers. These compiler drivers
typically support a command line argument that can be used to specify where
these tools can be found. CMake provides the
CMAKE_<LANG>_COMPILER_EXTERNAL_TOOLCHAIN variable which can be used to
specify the directory in which these utilities are located.
在许多情况下,工具链就足够了,但有时项目可能需要访问更广泛的库、头文件等,因为它们可以在目标平台上找到。处理此问题的常见方法是为构建提供目标平台的根文件系统的缩减版本(甚至完整版本)。这称为系统根目录或简称为sysroot 。sysroot 基本上只是目标平台的根文件系统安装或复制到可以通过主机的文件系统访问的路径。工具链包通常提供一个最小的系统根目录,其中包含编译和链接所需的各种库等。
In many cases, the toolchain is all that is needed, but sometimes projects may require access to a broader set of libraries, header files, etc. as they would be found on the target platform. A common way of handling this is to provide the build with a cut down version (or even a full version) of the root filesystem for the target platform. This is referred to as a system root or just sysroot for short. A sysroot is basically just the target platform’s root file system mounted or copied to a path that can be accessed through the host’s file system. Toolchain packages often provide a minimal sysroot containing various libraries, etc. needed for compiling and linking.
CMake 对 sysroots 具有相当广泛且易于使用的支持。工具链文件可以将CMAKE_SYSROOT变量设置为 sysroot 位置,仅凭该信息,CMake 就可以优先在 sysroot 区域中查找库、头文件等,而不是主机上的同名文件(这在第24.1.2 节中有详细介绍) “交叉编译控制”)。在许多情况下,CMake 还会自动向底层工具添加必要的编译器/链接器标志,以使它们了解 sysroot 区域。对于需要提供不同的 sysroot 进行编译和链接的更复杂的场景(例如,由具有统一标头的 Android NDK 使用),可以
在使用 CMake 3.9 或更高版本时设置工具链文件CMAKE_SYSROOT_COMPILE和。CMAKE_SYSROOT_LINK
CMake has fairly extensive and easy to use support for sysroots. Toolchain
files can set the CMAKE_SYSROOT variable to the sysroot location and with
that information alone, CMake can find libraries, headers, etc. preferentially
in the sysroot area over same-named files on the host (this is covered in
detail in Section 24.1.2, “Cross-compilation Controls”). In many cases, CMake will also
automatically add the necessary compiler/linker flags to the underlying tools
to make them aware of the sysroot area. For more complex scenarios where
different sysroots need to be provided for compiling and linking (e.g. as used
by the Android NDK with unified headers), toolchain files can set
CMAKE_SYSROOT_COMPILE and CMAKE_SYSROOT_LINK instead when using
CMake 3.9 or later.
在某些安排中,开发人员可以选择将完整的目标文件系统安装在主机安装点下,并将其用作系统根。可以将其安装为只读,或者如果不是,则可能仍然需要使其不被构建修改。因此,当项目构建完成后,可能需要将其安装到其他地方,而不是写入sysroot区域。CMake 提供了一个CMAKE_STAGING_PREFIX变量,可用于设置一个暂存点,低于该暂存点的任何安装命令都将安装到该暂存点(
有关此区域的讨论,请参阅第 26.1.2 节“基本安装位置” )。该暂存区域可以是正在运行的目标系统的安装点,并且可以在安装后立即测试已安装的二进制文件。当在快速主机上为目标系统进行交叉编译时,这种安排特别有用,否则构建速度会很慢(例如,在桌面计算机上为 Raspberry Pi 目标构建)。第 24.1.2 节“交叉编译控制”还讨论了如何
CMAKE_STAGING_PREFIX影响 CMake 搜索库、头文件等的方式。
In some arrangements, developers may choose to mount the full target file
system under a host mount point and use that as their sysroot. This could be
mounted as read-only, or if not it may still be desirable to leave it
unmodified by the build. Therefore, when the project has been built, it may
need to be installed to somewhere else rather than writing to the sysroot area.
CMake provides the CMAKE_STAGING_PREFIX variable which can be used to set
a staging point below which any install commands will install to (see
Section 26.1.2, “Base Install Location” for a discussion of this area). This staging area could
be a mount point for a running target system and the installed binaries could
then be tested immediately after installation. Such an arrangement is
particularly useful when cross compiling on a fast host for a target system
that would otherwise be slow to build on (e.g. building on a desktop machine
for a Raspberry Pi target). Section 24.1.2, “Cross-compilation Controls” also discusses how
CMAKE_STAGING_PREFIX affects the way CMake searches for libraries, headers
and so on.
当project()orenable_language()调用触发编译器和语言功能的测试时,try_compile()会在内部调用该命令来执行各种检查。如果提供了工具链文件,则每次try_compile()调用都会读取该文件,因此测试项目将以与主构建类似的方式进行配置。CMake 将自动传递一些相关变量,例如CMAKE_<LANG>_FLAGS,但工具链文件可能还希望将其他变量传递到测试构建。由于主构建将首先读取工具链文件,因此工具链文件本身可以定义应传递哪些变量来测试构建。这是通过将变量的名称添加到CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
变量中来完成的(不要在项目中设置,仅在工具链文件中设置)。使用
list(APPEND)而不是这样set()CMake 添加的任何变量都不会丢失。如果最终包含重复项并不重要CMAKE_TRY_COMPILE_PLATFORM_VARIABLES,重要的是存在所需的变量名称。
When a project() or enable_language() call triggers testing of compiler and
language features, the try_compile() command is called internally to
perform various checks. If a toolchain file has been provided, it is read by
each try_compile() invocation, so the test project will be configured in a
similar way to the main build. CMake will pass through some relevant variables
automatically, such as CMAKE_<LANG>_FLAGS, but toolchain files may want other
variables to be passed through to the test build as well. Since the main build
will read the toolchain file first, the toolchain file itself can define which
variables should be passed through to test builds. This is done by adding the
names of the variables to the CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
variable (do not set this in the project, only in a toolchain file). Use
list(APPEND) rather than set() so that any variables added by CMake are not
lost. It won’t matter if CMAKE_TRY_COMPILE_PLATFORM_VARIABLES ends up
containing duplicates, it only matters that the desired variable names are
present.
该try_compile()命令通常编译并链接测试代码以生成可执行文件。在某些交叉编译场景中,如果运行链接器需要自定义标志或链接器脚本,或者不希望调用(裸机目标平台的交叉编译可能有这样的限制),这可能会出现问题。CMAKE_TRY_COMPILE_TARGET_TYPE
如果使用 CMake 3.6 或更高版本,则可以通过设置为 来告诉命令创建静态库STATIC_LIBRARY。这避免了对链接器的需要,但它仍然需要归档工具。CMAKE_TRY_COMPILE_TARGET_TYPE也可以有 value
EXECUTABLE,如果没有设置值,这就是默认行为。在 CMake 3.6 之前,CMakeForceCompiler必须使用现已弃用的模块来防止try_compile()被调用,但 CMake 现在严重依赖这些测试来确定编译器支持哪些功能,因此
CMakeForceCompiler现在不鼓励使用。
The try_compile() command normally compiles and links test code to produce an
executable. In some cross compiling scenarios, this can present a problem if
running the linker requires custom flags or linker scripts, or is otherwise not
desirable to invoke (cross compiling for a bare metal target platform may have
such a restriction). If using CMake 3.6 or later, the command can be told to
create a static library instead by setting CMAKE_TRY_COMPILE_TARGET_TYPE
to STATIC_LIBRARY. This avoids the need for the linker, but it still requires
an archiving tool. CMAKE_TRY_COMPILE_TARGET_TYPE can also have the value
EXECUTABLE, which is the default behavior anyway if no value is set. Prior to
CMake 3.6, the now deprecated CMakeForceCompiler module had to be used to
prevent try_compile() from being invoked at all, but CMake now relies heavily
on these tests to work out what features the compilers support, so the use of
CMakeForceCompiler is now actively discouraged.
虽然在编译器检查期间不会调用它,但该try_run()命令与交叉编译密切相关try_compile(),并且其行为受到交叉编译的影响。try_run()实际上是try_compile()尝试运行刚刚构建的可执行文件。当CMAKE_CROSSCOMPILING设置为 true 时,CMake 会修改其运行测试可执行文件的逻辑。如果
CMAKE_CROSSCOMPILING_EMULATOR设置了该变量,CMake 会将其添加到原本用于在目标平台上运行可执行文件的命令之前,并使用该命令在主机平台上运行可执行文件。如果
为 trueCMAKE_CROSSCOMPILING_EMULATOR时未设置CMAKE_CROSSCOMPILING,则 CMake 需要工具链或项目手动设置一些缓存变量。这些变量提供退出代码以及stdout如果stderr
可执行文件能够在目标平台上运行则将获得的输出。必须手动提供这些显然不方便且容易出错,因此项目通常应尽量避免在无法设置的try_run()
交叉编译情况下进行调用。CMAKE_CROSSCOMPILING_EMULATOR对于无法避免这些手动定义变量的情况,该try_run()命令的 CMake 文档提供了有关要设置的变量的必要详细信息。第 25.7 节“交叉编译和仿真器”CMAKE_CROSSCOMPILING_EMULATOR中还讨论了
的进一步使用
。
While it is not invoked during compiler checks, the try_run() command is
closely related to try_compile() and its behavior is affected by
cross-compilation. try_run() is effectively a try_compile() followed by an
attempt to run the executable just built. When CMAKE_CROSSCOMPILING is
set to true, CMake modifies its logic for running the test executable. If the
CMAKE_CROSSCOMPILING_EMULATOR variable is set, CMake will prepend it to
the command that would otherwise have been used to run the executable on the
target platform and uses that to run the executable on the host platform. If
CMAKE_CROSSCOMPILING_EMULATOR is not set when CMAKE_CROSSCOMPILING is true,
CMake requires the toolchain or project to manually set some cache variables.
These variables provide the exit code and the output from stdout and stderr
that would be obtained had the executable been able to be run on the target
platform. Having to provide these manually is clearly inconvenient and
error-prone, so projects should generally try hard to avoid calling try_run()
in cross-compiling situations where CMAKE_CROSSCOMPILING_EMULATOR cannot be
set. For cases where these manually defined variables cannot be avoided, the
CMake documentation for the try_run() command provides the necessary details
regarding the variables to be set. Further uses of
CMAKE_CROSSCOMPILING_EMULATOR are also discussed in
Section 25.7, “Cross-compiling And Emulators”.
选择以下示例是为了突出本章讨论的概念。CMake 参考文档的工具链部分包含针对各种不同目标平台的更多示例。
The examples that follow have been selected to highlight the concepts discussed in this chapter. The toolchains section of the CMake reference documentation contains further examples for a variety of different target platforms.
Raspberry Pi 的交叉编译很好地介绍了 CMake 处理交叉编译的一般方式。第一步是获取编译器工具链,最常见的方法是使用 crosstool-NG 等实用程序。本示例的其余部分将用于/path/to/toolchain引用工具链目录结构的顶部。
Cross compiling for the Raspberry Pi is a good introduction to the way CMake
handles cross compilation in general. The first step is to obtain the compiler
toolchain, the most common way being to use a utility like crosstool-NG.
The rest of this example will use /path/to/toolchain to refer to the top of
the toolchain directory structure.
Raspberry Pi 的典型工具链文件可能如下所示:
A typical toolchain file for the Raspberry Pi might look something like this:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR ARM)
set(CMAKE_C_COMPILER
/path/to/toolchain/bin/armv8-rpi3-linux-gnueabihf-gcc
)
set(CMAKE_CXX_COMPILER
/path/to/toolchain/bin/armv8-rpi3-linux-gnueabihf-g++
)
set(CMAKE_SYSROOT
/path/to/toolchain/armv8-rpi3-linux-gnueabihf/sysroot
)set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR ARM)
set(CMAKE_C_COMPILER
/path/to/toolchain/bin/armv8-rpi3-linux-gnueabihf-gcc
)
set(CMAKE_CXX_COMPILER
/path/to/toolchain/bin/armv8-rpi3-linux-gnueabihf-g++
)
set(CMAKE_SYSROOT
/path/to/toolchain/armv8-rpi3-linux-gnueabihf/sysroot
)
如果主机具有正在运行的目标设备的安装点,则可以使用它来相对简单地测试项目构建的二进制文件。例如,假设/mnt/rpiStage是一个附加到正在运行的 Raspberry Pi 的安装点(这最好指向某个本地目录而不是系统根目录,以便可以以任意方式擦除或以其他方式修改它,而不会破坏运行系统的稳定性)。工具链文件会将此安装点指定为暂存区域,如下所示:
If the host has a mount point for a running target device, it could be used to
make testing the binaries built by the project relatively straightforward. For
example, assume /mnt/rpiStage is a mount point that attaches to a running
Raspberry Pi (this would preferably point to some local directory rather than
the system root so that it could be wiped or otherwise modified in arbitrary
ways without destabilizing the running system). A toolchain file would specify
this mount point as a staging area like so:
set(CMAKE_STAGING_PREFIX /mnt/rpiStage)set(CMAKE_STAGING_PREFIX /mnt/rpiStage)
然后,项目的二进制文件可以安装到该暂存区域并直接在设备上运行(请参阅第 26.1.2 节“基本安装位置”)。
The project’s binaries could then be installed to this staging area and run directly on the device (see Section 26.1.2, “Base Install Location”).
-m32
GCC 允许通过将标志添加到编译器和链接器命令来在 64 位主机上构建 32 位二进制文件。以下工具链示例仍然允许在 上找到 GCC 编译器PATH,只是将额外的标志添加到编译器和链接器使用的初始集中。根据个人的观点,这种安排可以被视为交叉编译,也可以不是。因此,设置CMAKE_SYSTEM_NAME也可以被视为可选,因为设置它会强制CMAKE_CROSSCOMPILING值为 true。无论哪种方式,CMAKE_SYSTEM_PROCESSOR仍然应该设置,因为该工具链文件的目标是专门针对与主机不同的处理器。
GCC allows 32-bit binaries to be built on 64-bit hosts by adding the -m32
flag to both the compiler and linker commands. The following toolchain example
still allows the GCC compilers to be found on the PATH, adding just the extra
flag to the initial set used by the compilers and linker. Depending on one’s
point of view, this arrangement could be seen as cross-compiling or not.
Therefore, setting CMAKE_SYSTEM_NAME could also be seen as optional, since
setting it forces CMAKE_CROSSCOMPILING to have the value true. Either way,
the CMAKE_SYSTEM_PROCESSOR should still be set since the goal of this
toolchain file is specifically to target a processor different to that of the
host.
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR i686)
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)
set(CMAKE_C_FLAGS_INIT -m32)
set(CMAKE_CXX_FLAGS_INIT -m32)
set(CMAKE_EXE_LINKER_FLAGS_INIT -m32)
set(CMAKE_SHARED_LINKER_FLAGS_INIT -m32)
set(CMAKE_MODULE_LINKER_FLAGS_INIT -m32)set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR i686)
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)
set(CMAKE_C_FLAGS_INIT -m32)
set(CMAKE_CXX_FLAGS_INIT -m32)
set(CMAKE_EXE_LINKER_FLAGS_INIT -m32)
set(CMAKE_SHARED_LINKER_FLAGS_INIT -m32)
set(CMAKE_MODULE_LINKER_FLAGS_INIT -m32)
确认构建确实是 32 位的一种方法是使用
CMAKE_SIZEOF_VOID_P变量,该变量由 CMake 作为其工具链设置的一部分自动计算。对于 64 位构建,该值为 8,而对于 32 位构建,该值为 4。
One way to confirm that the build is indeed 32-bit is with the
CMAKE_SIZEOF_VOID_P variable, which is computed by CMake automatically as
part of its toolchain setup. For 64-bit builds, this will have a value of 8,
whereas for 32-bit builds, it will be 4.
math(EXPR bitness "${CMAKE_SIZEOF_VOID_P} * 8")
message("${bitness}-bit build")math(EXPR bitness "${CMAKE_SIZEOF_VOID_P} * 8")
message("${bitness}-bit build")
Android 的交叉编译可能比迄今为止介绍的基本案例复杂一些,并且目标系统的描述方式也存在一些差异。要使用 CMake 的内置 Android 支持,CMAKE_SYSTEM_NAME必须设置为
Android,但CMAKE_SYSTEM_PROCESSOR通常不设置,并且 的值
CMAKE_SYSTEM_VERSION通常由 CMake 确定。许多 Android 特定的变量控制工具链配置,而不是设置各个编译器和工具的路径。使用的 CMake 生成器类型也会影响可用选项,不同的生成器支持不同的开发环境。例如,使用 Visual Studio 生成器时,CMake 3.18 或更早版本需要安装 Nvidia Nsight Tegra Visual Studio 版本。另一方面,使用 Ninja 或其中一个 Makefile 生成器允许开发人员选择使用 Android NDK 或独立工具链。CMake 3.19 或更高版本还允许 NDK 与 Visual Studio 一起使用。
Cross-compiling for Android can be a bit more involved than the basic cases
presented thus far and there are some differences in how the target system is
described.
To use CMake’s built-in Android support, CMAKE_SYSTEM_NAME must be set to
Android, but CMAKE_SYSTEM_PROCESSOR is not typically set and the value of
CMAKE_SYSTEM_VERSION is often left up to CMake to determine.
Rather than setting paths to individual compilers and tools, a number of
Android-specific variables control the toolchain configuration.
The type of CMake generator used also affects the available options, with
different generators supporting different development environments.
For instance, when using a Visual Studio generator, CMake 3.18 or earlier
requires the Nvidia Nsight Tegra Visual Studio Edition to be installed.
On the other hand, using Ninja or one of the Makefile generators allows the
developer to choose between using the Android NDK or a standalone toolchain.
CMake 3.19 or later also allows the NDK to be used with Visual Studio.
对于 Ninja 或 Makefile 生成器,CMake 必须确定是否应该使用 NDK 还是独立工具链。CMake 工具链文档中清楚地详细说明了选择逻辑,但进一步分解步骤可能会有所帮助(使用下面的第一个匹配项):
For Ninja or Makefile generators, CMake has to determine whether it should use an NDK or standalone toolchain. The selection logic is clearly detailed in the CMake toolchain documentation, but it can be helpful to break the steps down further (the first match from the following is used):
CMAKE_ANDROID_NDK设置了该变量,则将使用该位置的 NDK。
CMAKE_ANDROID_STANDALONE_TOOLCHAIN设置了该变量,则将使用该位置的独立工具链。该位置必须有一个sysroot子目录。
CMAKE_ANDROID_NDK variable is set, the NDK at that location will
be used.
CMAKE_ANDROID_STANDALONE_TOOLCHAIN variable is set, the
standalone toolchain at that location will be used. This location must have
a sysroot subdirectory.
CMAKE_SYSROOT
CMAKE_SYSROOT
CMAKE_SYSROOT设置为 形式的目录
<ndk>/platforms/android-<api>/arch-<arch>,则就像
CMAKE_ANDROID_NDK设置为<ndk>路径的一部分一样。<api>如果工具链文件未明确提供,则默认 Android API 级别将设置为路径的一部分(见下文)。
CMAKE_SYSROOT设置为表单的目录<someDir>/sysroot,那么它将就像CMAKE_ANDROID_STANDALONE_TOOLCHAIN已设置为<someDir>.
CMAKE_SYSROOT is set to a directory of the form
<ndk>/platforms/android-<api>/arch-<arch>, then it will be as though
CMAKE_ANDROID_NDK had been set to the <ndk> part of the path. The default
Android API level will be set to the <api> part of the path if not
explicitly provided by the toolchain file (see below).
CMAKE_SYSROOT is set to a directory of the form <someDir>/sysroot,
then it will be as though CMAKE_ANDROID_STANDALONE_TOOLCHAIN had been set
to <someDir>.
ANDROID_NDK设置了,它将被视为CMAKE_ANDROID_NDK
已设置。新项目应该不希望依赖于此,而是使用该
CMAKE_ANDROID_NDK变量。
ANDROID_STANDALONE_TOOLCHAIN设置了,它将被视为CMAKE_ANDROID_STANDALONE_TOOLCHAIN已设置。新项目应该不希望依赖于此,而是使用该
CMAKE_ANDROID_STANDALONE_TOOLCHAIN变量。
ANDROID_NDK is set, it will be treated as though CMAKE_ANDROID_NDK
had been set. New projects should prefer not to rely on this and use the
CMAKE_ANDROID_NDK variable instead.
ANDROID_STANDALONE_TOOLCHAIN is set, it will be treated
as though CMAKE_ANDROID_STANDALONE_TOOLCHAIN had been set. New projects
should prefer not to rely on this and use the
CMAKE_ANDROID_STANDALONE_TOOLCHAIN variable instead.
ANDROID_NDK_ROOT或ANDROID_NDK环境变量,它们将用作 CMake 变量的值CMAKE_ANDROID_NDK。
ANDROID_STANDALONE_TOOLCHAIN设置了环境变量,它将用作CMAKE_ANDROID_STANDALONE_TOOLCHAINCMake 变量的值。
ANDROID_NDK_ROOT or ANDROID_NDK environment variables
are set, they will be used as the value for the CMAKE_ANDROID_NDK CMake
variable.
ANDROID_STANDALONE_TOOLCHAIN environment variable is set, it will
be used as the value for the CMAKE_ANDROID_STANDALONE_TOOLCHAIN CMake
variable.
截至 r17 版本,NDK 为开发人员提供了比独立工具链更多的灵活性。独立工具链针对单一架构和 API 级别,而 NDK 可能包含对多个工具链的支持,因此支持一系列架构、API 级别等。从 r18 NDK 版本开始,仅支持 Clang 工具链和单个 STL 实现。
Up to the r17 release, the NDK allows the developer a little more flexibility than a standalone toolchain. Whereas a standalone toolchain targets a single architecture and API level, the NDK may contain support for multiple toolchains and hence a range of architectures, API levels, etc. As of the r18 NDK release, only the Clang toolchain and a single STL implementation are supported.
以下是与 NDK 和独立工具链更相关的变量的选择:
The following is a selection of the more relevant variables for NDK and standalone toolchains:
CMAKE_SYSTEM_VERSION
CMAKE_SYSTEM_VERSION
CMAKE_ANDROID_API已设置变量并使用该变量(如果可用)。否则,如果CMAKE_SYSROOT已设置,CMake 将尝试从 NDK 目录结构中检测 API 级别。如果也失败,将使用 NDK 支持的最新 API 级别。对于独立工具链, 的值CMAKE_SYSTEM_VERSION始终由工具链自动确定。
CMAKE_ANDROID_API variable has been set and uses that if available.
Otherwise, if CMAKE_SYSROOT has been set, CMake will try to detect the API
level from the NDK directory structure. If that also fails, the latest API
level supported by the NDK will be used. For a standalone toolchain, the value
of CMAKE_SYSTEM_VERSION is always determined automatically from the
toolchain.
CMAKE_ANDROID_ARCH_ABI
CMAKE_ANDROID_ARCH_ABI
armeabi最高 r16 的 NDK 版本,或可用于更高版本的最旧的 Arm ABI。CMAKE_ANDROID_ARCH_ABI如果 NDK 具有必要的体系结构支持,则可以指定其他值(例如、arm64-v8a、
armeabi-v7a、armeabi-v6、mips或mips64)。使用独立工具链时会自动设置此变量。的值
将源自提供相应的更通用的架构值,该值将是
、、、或之一。x86x86_64CMAKE_ANDROID_ARCHCMAKE_ANDROID_ARCH_ABIarmarm64mipsmips64x86x86_64
armeabi for NDK releases up to r16, or the oldest arm ABI
available for later releases. CMAKE_ANDROID_ARCH_ABI can be given other
values where the NDK has the necessary architecture support (e.g. arm64-v8a,
armeabi-v7a, armeabi-v6, mips, mips64, x86 or x86_64). This
variable is set automatically when using a standalone toolchain. The value of
CMAKE_ANDROID_ARCH will be derived from CMAKE_ANDROID_ARCH_ABI to
provide the corresponding more general architecture value, which will be one of
arm, arm64, mips, mips64, x86 or x86_64.
CMAKE_ANDROID_ARM_MODE
CMAKE_ANDROID_ARM_MODE
CMAKE_ANDROID_ARCH_ABI设置为其中一种armeabi*架构时,开发人员可以在 32 位 ARM 或 16 位 Thumb 处理器之间进行选择。如果
CMAKE_ANDROID_ARM_MODE设置为布尔 true 值,则将选择 ARM 处理器,否则如果设置为 false 或根本不设置,则 Thumb 将是目标处理器。无论使用 NDK 还是独立工具链都可以进行设置。
CMAKE_ANDROID_ARCH_ABI is set to one of the armeabi* architectures,
developers can choose between 32-bit ARM or 16-bit Thumb processors. If
CMAKE_ANDROID_ARM_MODE is set to a boolean true value, the ARM processor will
be selected, otherwise if set to false or not set at all, Thumb will be the
target processor. This can be set whether using the NDK or a standalone
toolchain.
CMAKE_ANDROID_ARM_NEON
CMAKE_ANDROID_ARM_NEON
CMAKE_ANDROID_ARCH_ABI设置为 时armeabi-v7a,CMAKE_ANDROID_ARM_NEON
可以设置为布尔真值以启用 NEON 支持。无论使用 NDK 还是独立工具链都可以进行设置。
CMAKE_ANDROID_ARCH_ABI is set to armeabi-v7a, CMAKE_ANDROID_ARM_NEON
can be set to a boolean true value to enable NEON support. This can be set
whether using the NDK or a standalone toolchain.
CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION
CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION
X.Y- GCC 版本 XY
clangX.Y- Clang 版本 XY
clang- 最新可用的 Clang 版本
如果未设置此变量,则在使用 CMake 3.12.1 或更早版本时将使用 NDK 中可用的最新 GCC 版本。NDK r18 完全删除了对 GCC 的支持,并且认识到这一点,如果 GCC 不可用,CMake 3.12.2 及更高版本默认使用 Clang。建议项目请求clang工具链或接受默认工具链,以避免 NDK 版本和 CMake 版本之间的这些差异。
X.Y - GCC version X.Y
clangX.Y - Clang version X.Y
clang - Latest available Clang version
If this variable is not set, the latest GCC version available in the NDK will
be used when using CMake 3.12.1 or earlier.
NDK r18 removed support for GCC altogether and in recognition of this,
CMake 3.12.2 and later uses Clang by default if GCC is not available.
Projects are advised to request clang for the toolchain or accept the default
toolchain to avoid these differences across NDK releases and CMake versions.
CMAKE_ANDROID_STL_TYPE
CMAKE_ANDROID_STL_TYPE
none
system
gabi++_static
gabi++_shared
gnustl_static
gnustl_shared
c++_static
c++_shared
stlport_static
stlport_shared
如果未给出,则默认为gnustl_static,尽管 CMake 3.12.2 及更高版本将选择c++_staticifgnustl_static不可用。gnustl_*请注意,从 NDK r18 开始,不支持与 STL 实现紧密相关的GCC 工具链,并且该工具链在较旧的 NDK 中最多仅支持 C++11。这些stlport_*实现甚至更旧、更原始,甚至不支持 C++11。该none选项根本不支持 C++,并且该system选项只有newSTL,delete但没有 STL。
从 NDK r18 开始,仅c++_static和c++_sharedSTL 类型可用(system也none可能可用,但通常不应使用)。因此,建议项目请求c++_*
STL 实现之一(这些是 LLVM C++ 标准库实现)。
每个 CMake 目标都有自己的ANDROID_STL_TYPE属性,并且该
CMAKE_ANDROID_STL_TYPE变量用于提供该属性的初始值。在大多数情况下,希望在整个构建过程中使用相同的 STL 类型,因此使用变量而不是设置单独的目标属性可能更简单、更稳健。
none
system
gabi++_static
gabi++_shared
gnustl_static
gnustl_shared
c++_static
c++_shared
stlport_static
stlport_shared
If not given, the default is gnustl_static, although CMake 3.12.2 and later
will select c++_static instead if gnustl_static is not available.
Note that the GCC toolchain to which the gnustl_* STL implementations are
closely tied is not supported as of NDK r18 and that toolchain only supports up
to C++11 in older NDKs anyway.
The stlport_* implementations are even older and more primitive and
do not even support C++11.
The none option has no support for C++ at all and the system option has
only new and delete but no STL.
As of NDK r18, only the c++_static and c++_shared STL types
are available (system and none may also be available, but should not
generally be used).
It is therefore recommended that projects request one of the c++_*
STL implementations (these are the LLVM C++ standard library implementations).
Each CMake target has its own ANDROID_STL_TYPE property and the
CMAKE_ANDROID_STL_TYPE variable is used to provide the initial value of that
property. In most cases, it will be desirable to use the same STL type
throughout the build, so using the variable rather than setting individual
target properties is likely to be simpler and more robust.
NDK 构建的工具链文件的最小示例如下所示:
A minimal example of a toolchain file for a NDK build would look something like this:
set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_ANDROID_NDK /path/to/android-ndk)set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_ANDROID_NDK /path/to/android-ndk)
这将使用 NDK 中可用的最新 API 级别。对于 NDK r17 或更早版本,它将使用最新的 GCC 工具链和
gnustl_staticSTL 实现,而对于 NDK r18 及更高版本,它将使用 Clang 工具链和c++_staticSTL 实现。目标架构也将依赖于 NDK, armeabi
为 r16 或更早版本选择不带 neon 支持的(Thumb 处理器),或者为更高版本的 NDK 选择更新的架构。
This would use the latest API level available in the NDK.
For NDK r17 or earlier, it would use the latest GCC toolchain with the
gnustl_static STL implementation, whereas for NDK r18 and later, the Clang
toolchain would be used with the c++_static STL implementation.
The target architecture would also be NDK-dependent, selecting armeabi
(Thumb processors) without neon support for r16 or earlier, or a more recent
architecture for later NDK versions.
未指定工具链、STL 和架构并受 NDK 版本差异的影响,可能会使构建的可追溯性和健壮性降低。建议使用明确设置这些内容的工具链文件,以便这些特征明确。例如:
Having the toolchain, STL and architecture unspecified and subject to NDK version differences can make a build less traceable and less robust. It is advisable to use a toolchain file that explicitly sets these things so that these characteristics are unambiguous. For example:
set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_SYSTEM_VERSION 26) # API level
set(CMAKE_ANDROID_NDK /path/to/android-ndk)
set(CMAKE_ANDROID_ARCH_ABI arm64-v8a)
set(CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION clang)
set(CMAKE_ANDROID_STL_TYPE c++_shared)set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_SYSTEM_VERSION 26) # API level
set(CMAKE_ANDROID_NDK /path/to/android-ndk)
set(CMAKE_ANDROID_ARCH_ABI arm64-v8a)
set(CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION clang)
set(CMAKE_ANDROID_STL_TYPE c++_shared)
上面使用了最新的 Clang 工具链和共享 STL 运行时,支持更新的 C++ 标准。它还适用于 NDK r19 或更高版本,其中仅 Clang 工具链和基于 LLVM 的 STL 实现可用。
The above uses the latest Clang toolchain and a shared STL runtime with support for more recent C++ standards. It will also work for NDK r19 or later where only the Clang toolchain and the LLVM-based STL implementation are available.
相比之下,独立的工具链文件通常非常简单,因为许多配置决策是由工具链本身预先确定的:
In comparison, a standalone toolchain file is typically going to be very simple, since many of the configuration decisions are predetermined by the toolchain itself:
set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_ANDROID_STANDALONE_TOOLCHAIN
/path/to/android-toolchain
)set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_ANDROID_STANDALONE_TOOLCHAIN
/path/to/android-toolchain
)
某些工具可能会强制使用其自己的内部工具链文件,从而使开发人员更难指定上述任何设置。Android Studio 就是这样的一个例子,它强制使用一个特定的工具链文件来覆盖 CMake 自己的大部分逻辑。gradle 构建被设置为创建一个外部 CMake 构建,该构建使用 Ninja 生成器和通过 Android SDK 管理器提供的 NDK。虽然未启用对工具链文件的直接访问,但 gradle 构建确实提供了一系列 gradle 变量,这些变量会转换为 CMake 等效项。开发人员应查阅该工具的文档,以确定是否/如何使用不同的 CMake 版本以及如何影响 CMake 构建的行为。
Certain tools may enforce the use of their own internal toolchain file, making it potentially harder for developers to specify any of the above settings. Android Studio is one such example, forcing a particular toolchain file which overrides much of CMake’s own logic. The gradle builds are set up to create an external CMake build that uses the Ninja generator and the NDK provided through the Android SDK manager. While direct access to the toolchain file is not enabled, the gradle build does provide a range of gradle variables which are translated into their CMake equivalents. Developers should consult the tool’s documentation to determine if/how different CMake versions may be used and how to influence the behavior of the CMake build.
对于使用ndk-build(本质上只是 GNU 的包装器)而不是 gradle 的开发人员来说,CMake 3.7 添加了使用 导出文件作为 CMake 构建的一部分或作为安装步骤的一部分的make功能
。构建过程中的导出非常简单:Android.mkexport()install()
For developers using ndk-build (which is essentially just a wrapper around
GNU make) rather than gradle, CMake 3.7 added the ability to export an
Android.mk file as part of the CMake build using export() or as
part of the install step with install(). Exporting during the build is
straightforward:
export(TARGETS target1 [target2...] ANDROID_MK fileName)export(TARGETS target1 [target2...] ANDROID_MK fileName)
通常fileName会在Android.mk前面添加一些路径,以将其放置在 所需的位置ndk-build。每个指定的目标都将包含在生成的文件中,以及相关的使用要求,例如包含标志、编译器定义等。如果项目需要支持成为父级的一部分,这通常是项目想要做的事情ndk-build。对于 CMake 项目将被打包并希望使其易于合并到任何 中的情况ndk-build,该install()命令提供所需的功能(请参阅第 26.3 节 “安装导出”)。
The fileName will typically be Android.mk with some path prepended to put
it at the location required by ndk-build. Each of the named targets will be
included in the generated file along with the relevant usage requirements such
as include flags, compiler defines, etc. This is typically what a project will
want to do if it needs to support being part of a parent ndk-build. For the
case where the CMake project will be packaged up and wants to make itself easy
to incorporate into any ndk-build, the install() command offers the
required functionality (see Section 26.3, “Installing Exports”).
使用 Visual Studio 生成器时,CMake 3.19 或更高版本也支持 NDK。对于直接通过 Visual Studio 安装程序安装的 NDK 版本,安装程序应该相当顺利,但这些版本往往相当旧,因此用途有限。如果使用在 Visual Studio 之外安装的更新的 NDK 版本,Ninja 和 Makefile 生成器可能会提供更好的结果。
When using a Visual Studio generator, CMake 3.19 or later also supports the NDK. Setup should work reasonably smoothly for NDK versions installed directly via the Visual Studio installer, but these tend to be quite old and are therefore of limited usefulness. The Ninja and Makefiles generators are likely to give better results if using more recent NDK versions installed outside of Visual Studio.
使用 CMake 3.18 或更早版本时,必须改用 Nvidia Nsight Tegra Visual Studio Edition。通过这种方法,生成的项目将驱动整个构建,而不是形成更大的梯度或ndk-build结构的一部分。CMake 3.1 中首次添加了支持,但许多选项直到 CMake 3.4 才添加。生成器通常设置如下:
When using CMake 3.18 or earlier, the Nvidia Nsight Tegra Visual Studio
Edition must be used instead.
With this method, the resultant project will drive the whole build rather than
forming part of a larger gradle or ndk-build structure.
Support was first added in CMake 3.1, but many of the options were not added
until CMake 3.4.
The generator would typically be set like so:
cmake -G“Visual Studio 12 2013 Tegra-Android”\
-DCMAKE_TOOLCHAIN_FILE=/some/path/toolchain.cmake \
/路径/到/源cmake -G "Visual Studio 12 2013 Tegra-Android" \
-DCMAKE_TOOLCHAIN_FILE=/some/path/toolchain.cmake \
/path/to/source最小的工具链文件只需要设置CMAKE_SYSTEM_NAMEto
Android,但就像 NDK 和独立工具链情况一样,可以设置更多变量来影响目标架构等。在许多情况下,为 Visual Studio 构建设置的变量与 NDK 的情况不同,但通常是相关的。
A minimal toolchain file would only need to set the CMAKE_SYSTEM_NAME to
Android, but just like the NDK and standalone toolchain cases, further
variables can be set to influence the target architecture, etc. In a number of
cases, the variables to be set for Visual Studio builds are different to the
NDK case, but are often related.
虽然 NDK 和独立工具链构建可以设置CMAKE_ANDROID_ARCH_ABI
并允许CMAKE_ANDROID_ARCH从中派生,但 Visual Studio 构建的工具链文件可以CMAKE_ANDROID_ARCH直接设置。对于 Visual Studio 情况,允许的值也不同:armv7-a、armv7-a-hard、arm64-v8a和
。x86x86_64
Whereas NDK and standalone toolchain builds would set CMAKE_ANDROID_ARCH_ABI
and allow CMAKE_ANDROID_ARCH to be derived from it, toolchain files for Visual
Studio builds set CMAKE_ANDROID_ARCH directly. Allowable values are also
different for the Visual Studio case: armv7-a, armv7-a-hard, arm64-v8a,
x86 and x86_64.
同样,Visual Studio 构建的工具链文件将设置
CMAKE_ANDROID_API而不是CMAKE_SYSTEM_VERSION指定目标设备的 Android API 级别,并CMAKE_ANDROID_API充当ANDROID_API目标属性的默认值。此外,
CMAKE_ANDROID_API_MIN可以设置为指定用于构建本机代码的 API 版本(它遵循相同的模式并充当目标ANDROID_API_MIN属性的默认值)。这有点类似于 Apple 平台的情况,其中用于构建的 SDK 可以单独指定为目标设备的最低操作系统级别(请参阅
第 23.5 节“构建设置”)。
Similarly, toolchain files for Visual Studio builds would set
CMAKE_ANDROID_API rather than CMAKE_SYSTEM_VERSION to specify the
Android API level of the target device, with CMAKE_ANDROID_API acting as a
default value for the ANDROID_API target property. Furthermore,
CMAKE_ANDROID_API_MIN can be set to specify the API version to be used to
build the native code (it follows the same pattern and acts as the default
value for the ANDROID_API_MIN target property). This is somewhat
analogous to the situation for Apple platforms where the SDK used for the build
can be specified separately to the minimum OS level of the target device (see
Section 23.5, “Build Settings”).
该CMAKE_ANDROID_STL_TYPE变量提供目标属性的默认值
ANDROID_STL_TYPE。它接受与 NDK 情况类似的值,但不支持
c++_static和
。并可能作为替代方案提供,但用户应针对自己安装的工具集确认这一点。c++_sharedllvm-libc++_staticllvm-libc++_shared
The CMAKE_ANDROID_STL_TYPE variable provides the default value for the
ANDROID_STL_TYPE target property.
It accepts similar values to the NDK case, except that c++_static and
c++_shared are not supported.
llvm-libc++_static and llvm-libc++_shared may be available
as alternatives, but users should confirm this for their own set of installed
tools.
由于这种安排驱动整个构建,因此它必须设置的不仅仅是 CMake 构建的本机代码。还有许多其他目标属性与与构建本机代码无关的构建部分相关,例如 JAR 依赖项、java 源的设置等。其中一些目标属性还具有定义默认值的关联 CMake 变量。这些目标属性都具有以下形式的名称ANDROID_…,并且 CMake 默认变量具有以下形式CMAKE_ANDROID_…。这些详细信息超出了此处涵盖的材料的范围,因此感兴趣的读者应查阅 CMake 文档以获取有关受支持的属性和变量的详细信息,然后将它们设置为适合其项目的非本机部分。
Since this arrangement drives the whole build, it has to set up more than just
the native code built by CMake. There are a number of other target properties
that relate to the parts of the build not associated with building the
native code, such as settings for JAR dependencies, java sources, etc. Some of
these target properties also have associated CMake variables that define
defaults. These target properties all have names of the form ANDROID_… and
the CMake default variables have the form CMAKE_ANDROID_…. These details
are beyond the scope of the material covered here, so interested readers should
consult the CMake documentation for details on the supported properties and
variables, then set them as appropriate for the non-native parts of their
project.
工具链文件乍一看似乎有点令人生畏,但其中大部分来自许多示例和项目,其中包含了太多逻辑。工具链文件应该尽可能小以支持所需的工具,并且它们通常应该可以在不同的项目中重复使用。特定于项目的逻辑应该位于项目自己的CMakeLists.txt文件中。
Toolchain files can seem a little intimidating at first, but much of this comes
from many examples and projects putting too much logic in them. Toolchain
files should be as minimal as possible to support the required tools and they
should generally be reusable across different projects. Logic specific to a
project should be in the project’s own CMakeLists.txt files.
在编写工具链文件时,开发人员应确保内容不会假设它们只会执行一次。CMake 可能会多次处理工具链文件,具体取决于项目的用途(例如多次调用
project()或enable_language())。工具链文件也可以用于作为try_compile()调用的一部分“放在一边”的临时构建,因此它们不应该对使用它们的上下文做出任何假设。
When writing toolchain files, developers should ensure that the contents do not
assume they will only be executed once. CMake may process the toolchain file
multiple times depending on what the project does (e.g. multiple calls to
project() or enable_language()). The toolchain file may also be used for
temporary builds "off to the side" as part of try_compile() calls, so they
should make no assumptions about the context in which they are being used.
避免使用已弃用的CMakeForceCompiler模块来设置要在构建中使用的编译器。该模块在使用较旧的 CMake 版本时很受欢迎,但较新的版本严重依赖于测试工具链并确定其支持的功能。该CMakeForceCompiler模块主要用于 CMake 未知编译器的情况,但在最新的 CMake 版本中使用此类编译器可能会导致不小的限制。建议与 CMake 开发人员合作,为此类编译器添加所需的支持。
Avoid using the deprecated CMakeForceCompiler module to set the compiler to
be used in the build. This module was popular when using older CMake versions,
but newer versions rely heavily on testing the toolchain and working out the
features it supports. The CMakeForceCompiler module was mainly intended for
cases where the compiler was not known to CMake, but use of such compilers with
recent CMake versions will likely result in non-trivial limitations. It is
recommended to work with the CMake developers to add the required support for
such compilers.
请小心,不要丢弃或错误处理在处理工具链文件时可能已经设置的变量内容。一个常见的错误是修改像CMAKE_<LANG>_FLAGS而不是这样的
变量CMAKE_<LANG>_FLAGS_INIT,这可能会丢弃开发人员手动设置的值,或者在多次处理工具链文件时与已填充的值交互不良。
Be careful not to discard or mishandle the contents of variables that may
already be set by the time the toolchain file is processed. A common error is
to modify variables like CMAKE_<LANG>_FLAGS rather than
CMAKE_<LANG>_FLAGS_INIT, which can discard values manually set by developers
or interact poorly with values already populated when the toolchain file is
processed multiple times.
当针对 Android 平台时,更喜欢使用带有 NDK 的简单工具链文件和 Ninja 或 Makefile 生成器。这种组合具有最好的 CMake 支持并且最易于使用。即将发布的 NDK r23 版本预计将提供一个可以直接与 CMake 3.20.0 或更高版本一起使用的工具链文件。考虑在正式发布后测试 NDK r23,但请注意,它可能会使用与内置 CMake Android 支持不同的变量进行配置(NDK 工具链文件记录了它支持的变量)。对于早期的 NDK 或 CMake 版本,开发人员可以使用自己的工具链文件,也可能使用 NDK 提供的工具链文件,但可能存在一些限制。Android NDK r19 版本引入了一些更改,打破了对 CMake 3.15 及更早版本的内置 Android 支持。在这种情况下,至少使用 CMake 3.16,但最好至少使用 CMake 3.20,以利用进一步的相关错误修复。避免在线示例中经常引用的流行taka-no-me工具链文件。它过于复杂并且存在已知问题。
When targeting Android platforms, prefer to use a simple toolchain file with the NDK and a Ninja or Makefile generator. This combination has the best CMake support and is the easiest to use. The upcoming NDK r23 release is expected to provide a toolchain file that can be used directly and robustly with CMake 3.20.0 or later. Consider testing out NDK r23 once it is officially released, but note that it will likely use different variables for configuration than the built-in CMake Android support (the NDK toolchain file documents the variables it supports). For earlier NDK or CMake versions, developers may use their own toolchain file or potentially the toolchain file provided by the NDK, but there may be some limitations. The Android NDK r19 release introduced changes which broke the built-in Android support for CMake 3.15 and earlier. Use at least CMake 3.16 in this case, but prefer at least CMake 3.20 to take advantage of further relevant bug fixes. Avoid the popular taka-no-me toolchain file frequently referred to by online examples. It is overly complicated and has known issues.
项目通常应避免将CMAKE_CROSSCOMPILING变量用于其任何逻辑。该变量可能会产生误导,因为即使目标平台和主机平台相同,它也可以设置为 true,而当目标平台和主机平台不同时,它也可以设置为 false。其价值的不一致使其不可靠。项目作者还应该意识到,一些多配置生成器(例如 Xcode)允许在构建时选择目标平台,因此需要非常仔细地编写基于是否交叉编译的 CMake 逻辑,以处理不同的情况项目可能会生成。
Projects should generally avoid using the CMAKE_CROSSCOMPILING variable for
any of its logic. This variable can be misleading, since it can be set to true
even when the target and host platform are the same, or false when they are
different. The inconsistency of its value makes it unreliable. Project authors
should also be aware that some multi configuration generators (e.g. Xcode)
allow the target platform to be selected at build time, so CMake logic based
around whether cross-compiling or not needs to be written very carefully to
handle the different situations in which the project may be generated.
工具链文件通常包含用于修改 CMake 搜索程序、库和其他文件位置的命令。请参阅第 24 章“查找事物” ,了解与此领域相关的推荐实践。
Toolchain files often contain commands to modify where CMake searches for programs, libraries and other files. See Chapter 24, Finding Things for recommended practices related to this area.
Apple 平台具有许多独特的特征,这些特征直接影响软件的构建方式。虽然 macOS 的简单命令行应用程序可以采用与其他基于 Unix 的平台类似的方式构建,但那些具有图形用户界面的应用程序通常以 Apple 特定的格式提供,称为应用程序包(或简称为app 包)。这些捆绑包不仅仅是一个可执行文件,它们是包含与应用程序关联的各种文件的标准化目录结构。这些应用程序包旨在是独立的,能够作为一个单元移动并放置在用户文件系统上的任何位置。
Apple platforms have a number of unique characteristics which directly affect the way software is built. While simple command-line applications for macOS can be built in similar ways to other Unix-based platforms, those applications with a graphical user interface are usually provided in an Apple-specific format known as an application bundle (or just app bundle). These bundles are more than a single executable file, they are a standardized directory structure containing a variety of files associated with the application. These app bundles are intended to be self-contained, able to be moved around as a unit and placed anywhere on a user’s file system.
图书馆也存在类似的情况。独立的静态和共享库的创建方式与其他基于 Unix 的平台上的库非常相似,但它们也可以构建为框架的一部分,框架本质上相当于应用程序包的库。框架有自己的标准化目录结构,并且可能包含库二进制文件以外的文件。它们甚至可能支持该目录结构中的多个版本。打算在运行时加载的库可以构建为可加载包,这与 Apple 的CFBundle功能相对应。
There is an analogous situation for libraries too. Standalone static and shared
libraries can be created much like those on other Unix-based platforms, but
they can also be built as part of a framework, which is essentially the
library equivalent of an app bundle. Frameworks have their own standardized
directory structure and may contain files other than just the library binaries.
They may even support multiple versions within that directory structure.
Libraries intended to be loaded at runtime can instead be built as a loadable
bundle, which corresponds to Apple’s CFBundle functionality.
捆绑包和框架是用于为苹果应用商店生成内容的机制的重要组成部分。另一个关键方面是代码签名,这是一个验证软件完整性和来源的过程,是应用商店分发的强制性部分。代码权利也是构建过程中不可或缺的一部分,并控制代码可以使用哪些 Apple 功能。这些权利是代码签名过程密封的信息的一部分,如果默认权利集(为空)不合适,则必须在构建时定义这些权利。
Bundles and frameworks are essential parts of the machinery used to produce content for Apple’s app store. Another key aspect is code signing, a process which verifies the integrity and origin of software and is a mandatory part of app store distribution. Code entitlements are also an integral part of the build process and govern which Apple features the code may use. These entitlements are part of the information sealed by the code signing process and must be defined at build time if the default entitlement set (which is empty) is not appropriate.
这些功能共同为 CMake 项目带来了独特的挑战。接下来的部分提供了用于理解和处理这些领域的工具,或者在某些情况下,强调了 CMake 支持的当前限制。还需要注意的是,对 tvOS 和 watchOS 的正式支持是在 CMake 3.14 中才添加的(在此之前已经正式支持了 macOS 和 iOS)。
Together, these features present unique challenges for CMake projects. The sections that follow provide the tools for understanding and handling these areas, or in some cases, highlight the current limitations of CMake’s support. It should also be noted that formal support for tvOS and watchOS was only added in CMake 3.14 (macOS and iOS have been formally supported before that).
用于生成框架和捆绑包的技术和工具不断发展,主要的 Apple 操作系统版本经常引入新功能并改变签名、分发等方面的要求。这些流程和技术紧密集成到 Xcode 中,作为 Apple 期望开发人员的主要工具使用时,开发人员通常也希望升级到当前的 Xcode 版本,而不是保留过去的主要版本。资源编译、代码签名等领域作为构建应用程序和框架的一部分自动处理,其中许多方面都是 Apple 生态系统独有的。
The technologies and tools used to produce frameworks and bundles is constantly evolving, with major Apple OS releases often introducing new features and changing the requirements around signing, distribution, etc. The processes and technologies are tightly integrated into Xcode as the primary tool Apple expects developers to use, with developers also typically expected to upgrade to the current Xcode release rather than staying with past major releases. Areas like resource compilation, code signing, etc. are automatically handled as part of building applications and frameworks, many aspects of which are unique to the Apple ecosystem.
对于 CMake 项目,这意味着 Xcode 生成器是使用 Xcode 工具链构建最可靠且最方便的。其他生成器(例如 Makefiles 或 Ninja)往往缺乏 Xcode 生成器的一些自动化功能,或者它们可能落后于对某些最新 Xcode 功能的实现支持。除了不打算通过应用程序商店分发的基本未签名桌面应用程序之外,开发人员或多或少需要使用 Xcode 生成器来获得支持必要功能的构建版本。另请注意,Apple 平台的快速变化特性意味着开发人员通常也希望使用最新的 CMake 和 Xcode 版本来跟上变化。Apple Silicon 的到来就是一个很好的例子,在该架构上构建或针对该架构构建时,CMake 3.19.2 和 Xcode 12.2 应被视为最低版本。这些也是 CMake 能够使用 Xcode 的现代构建系统的最低要求。
For CMake projects, this means that the Xcode generator is the most reliable and most convenient for building with the Xcode toolchain. Other generators such as Makefiles or Ninja tend to lack some of the automation of the Xcode generator, or they may lag behind implementing support for some of the more recent Xcode features. With the exception of basic unsigned desktop applications not intended for distribution through the app store, developers will be more or less required to use the Xcode generator to get a build that supports the necessary features. Also note that the fast-moving nature of Apple platforms means that developers will also generally want to be using fairly recent CMake and Xcode releases to keep up with the changes. The arrival of Apple Silicon is a good example, where CMake 3.19.2 and Xcode 12.2 should be considered the minimum versions when building on or for that architecture. These are also the minimum requirements for CMake to be able to use Xcode’s modern build system.
Xcode 生成器的独特优势之一是它支持设置任意 Xcode 项目属性。大多数项目设置可以使用表单的目标属性以键值方式基于每个目标进行修改
XCODE_ATTRIBUTE_XXX,其中XXX是 Xcode 属性的名称。这些名称在 Apple 文档中定义,但查找它们的一种可能更方便的方法是打开现有的 Xcode 项目,转到目标的构建设置并选择菜单项Editor > Show Settings Names。或者,可以单击感兴趣的构建设置,快速帮助助手编辑器会显示设置名称以及说明。第三种方法是使用以下命令从命令行获取项目构建设置的列表xcodebuild:
One of the unique benefits of the Xcode generator is that it supports setting
arbitrary Xcode project attributes. Most project settings can be modified in a
key-value fashion on a per-target basis using target properties of the form
XCODE_ATTRIBUTE_XXX, where XXX is the name of an Xcode property. These
names are defined in the Apple documentation, but a potentially more convenient
way to find them is to open an existing Xcode project, go to the build settings
of a target and select the menu item Editor > Show Setting Names.
Alternatively, one can click on a build setting of interest and the Quick
Help assistant editor shows the setting name along with a description.
A third method is to obtain a listing of a project’s build settings from the
command line using xcodebuild:
xcodebuild -showBuildSettings
xcodebuild -showBuildSettings
CMake 还支持 形式的变量CMAKE_XCODE_ATTRIBUTE_XXX,但它们与相应目标属性的关系与通常的 CMake 排列不同。这些变量用于设置或覆盖 Xcode 项目中的全局默认值,而不是用于初始化目标属性。它们仅在顶级文件中有效CMakeLists.txt,在所有其他目录范围中它们的值将被忽略。XCODE_ATTRIBUTE_XXX设置目标属性后,它将覆盖全局默认值。
CMake also supports variables of the form CMAKE_XCODE_ATTRIBUTE_XXX,
but their relationship to the corresponding target properties is different to
the usual CMake arrangement.
These variables are used to set or override global defaults in the Xcode
project rather than being used to initialize target properties.
They only have an effect in the top level CMakeLists.txt file, their value is
ignored in all other directory scopes.
When a XCODE_ATTRIBUTE_XXX target property is set, it overrides the global
default.
以下示例显示了一些更常用的属性:
The following example shows some of the more commonly used attributes:
# Set the default signing identity and team ID to use for
# all targets, must be in the top level of the project
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY
"Apple Development"
)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM
XYZ123ABCD
)
# Target-specific settings, can be in any directory scope
set_target_properties(MyApp PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY 1,2
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 10.0
)# Set the default signing identity and team ID to use for
# all targets, must be in the top level of the project
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY
"Apple Development"
)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM
XYZ123ABCD
)
# Target-specific settings, can be in any directory scope
set_target_properties(MyApp PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY 1,2
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 10.0
)
此功能还可用于通过附加[variant=ConfigName]到属性名称来仅为一种特定构建类型设置 Xcode 属性。其他后缀类型也可以附加到属性名称中,以进行更具体的属性设置,但它们的使用并不常见。甚至[variant=…]
通常也不需要后缀。以下示例介绍了此功能可能有用的用例类型:
This feature can also be used to set an Xcode attribute for only one particular
build type by appending [variant=ConfigName] to the property name. Other
suffix types can be appended to the property name too for even more specific
attribute settings, but their use would be unusual. Even [variant=…]
suffixes would not often be needed. The following example gives an idea of the
sort of use cases where this feature might be useful:
set_target_properties(MyApp PROPERTIES
XCODE_ATTRIBUTE_GCC_UNROLL_LOOPS[variant=Release] YES
XCODE_ATTRIBUTE_ENABLE_TESTABILITY[variant=Debug] YES
)set_target_properties(MyApp PROPERTIES
XCODE_ATTRIBUTE_GCC_UNROLL_LOOPS[variant=Release] YES
XCODE_ATTRIBUTE_ENABLE_TESTABILITY[variant=Debug] YES
)
有些项目可能需要设置相当多的属性才能获得所需的 Xcode 行为和功能,而其他项目可能非常简单,只需要最少数量的附加设置。有些属性只在非常特殊的情况下才需要,而其他属性则非常常见,它们几乎(或应该)出现在每个以苹果为中心的项目中。本章的其余部分将讨论其中的一些内容,包括上面示例中使用的一些内容。
Some projects may require setting quite a few attributes in order to get the desired Xcode behavior and features, whereas other projects may be quite simple and require only a minimal number of additional settings. Some attributes are only needed in very specific circumstances, whereas others are so common they are (or should be) present in almost every Apple-focused project. A number of these are discussed in the rest of this chapter, including some of those used in the above examples.
应用程序包是苹果生态系统的基石。了解应用程序包的结构以及它如何影响构建的配置方式对于任何针对 Apple 平台的开发人员来说都是至关重要的。
Application bundles are a cornerstone of the Apple ecosystem. Understanding the structure of an application bundle and how this affects the way the build should be configured is essential for any developer targeting Apple platforms.
macOS 应用程序包的结构与 iOS、tvOS 和 watchOS 的应用程序包结构不同。macOS 结构将各种类别的文件分为不同的子目录,如下所示(应用程序可能只显示部分子目录):
The structure of an application bundle for macOS is different to that for iOS, tvOS and watchOS. The macOS structure separates out various categories of files into different subdirectories and looks something like the following (applications might have only some of the subdirectories shown):
相比之下,iOS、tvOS 和 watchOS 的捆绑包结构是扁平化的,几乎没有定义的结构:
In contrast, the bundle structure for iOS, tvOS and watchOS is flattened, having very little in the way of a defined structure:
在构建应用程序包时,CMake 在某种程度上抽象了这些结构差异,无论是为 macOS 还是为 iOS、tvOS 或 watchOS 构建应用程序包,至少允许以相同的方式处理某些事情。然而,开发人员应该意识到,直到更新的 CMake 版本(资源处理是一个具体示例)之前,该抽象的某些区域尚未正确实现,因此强烈建议使用最新的 CMake 版本。
When building an app bundle, CMake somewhat abstracts away these structural differences, allowing at least some things to be handled the same way whether the bundle is being built for macOS or for iOS, tvOS or watchOS. Developers should be aware, however, that some areas of that abstraction have not been implemented correctly until more recent CMake releases (the handling of resources being a specific example), so using the latest CMake release is strongly advised.
MACOSX_BUNDLE
通过将关键字添加到来将应用程序标识为捆绑包add_executable():
An application is identified as being a bundle by adding the MACOSX_BUNDLE
keyword to add_executable():
add_executable(MyApp MACOSX_BUNDLE ...)add_executable(MyApp MACOSX_BUNDLE ...)
这会将MACOSX_BUNDLEtarget 属性设置为 true,非 Apple 平台会忽略该属性。项目也可以将该
CMAKE_MACOSX_BUNDLE变量设置为 true,并且所有随后定义的可执行目标也设置其目标属性,但
在每个命令中MACOSX_BUNDLE使用关键字会更常见、更清晰(项目通常仅定义少量应用程序包,通常只有一个)。MACOSX_BUNDLEadd_executable()
This sets the MACOSX_BUNDLE target property to true, which non-Apple
platforms simply ignore. A project can alternatively set the
CMAKE_MACOSX_BUNDLE variable to true and all subsequently defined
executable targets have their MACOSX_BUNDLE target property set as well,
but it would be more common and clearer to use the MACOSX_BUNDLE
keyword with each add_executable() command instead (projects typically define
only a small number of application bundles, often only one).
有点令人困惑的是,它MACOSX_BUNDLE不仅适用于 macOS,还适用于 iOS、tvOS 和 watchOS。该关键字早于非桌面 Apple 平台,因此是桌面特定的名称。不是为每个其他平台创建新的关键字,而是将现有关键字的使用扩展到涵盖所有 Apple 平台。在许多其他情况下也可以看到这种扩展 OSX 特定关键字和变量以覆盖所有 Apple 平台的相同模式,但请注意,这并不适用于所有 OSX 相关变量和属性。本章重点介绍了那些适用这一点的内容。
Somewhat confusingly, MACOSX_BUNDLE applies not just to macOS, but also to
iOS, tvOS and watchOS. The keyword predates the non-desktop Apple platforms,
hence the desktop-specific name. Rather than creating new keywords for each of
the other platforms, the use of the existing keyword was expanded to cover all
of the Apple platforms. This same pattern of expanding OSX-specific keywords
and variables to cover all the Apple platforms can be seen in a number of other
cases as well, but note that this is not universal across all OSX-related
variables and properties. Those for which this holds true are highlighted in
this chapter.
每个应用程序包必须至少有一个Info.plist文件和一个主可执行文件(MyApp在上面的目录结构示例中)。默认情况下,CMake 将从Info.plistCMake 本身附带的模板文件中提供基本文件。然而,在大多数情况下,项目将希望提供自己的应用程序配置
Info.plist,以便他们可以完全控制应用程序配置。当应用程序使用故事板或界面构建器文件时,
Info.plist几乎需要提供自定义,以便
NSMainStoryboardFile出现相关的关键条目。targetMACOSX_BUNDLE_INFO_PLIST属性可以设置为用作模板的文件的名称Info.plist(适用于所有 Apple 平台,而不仅仅是 macOS)。默认模板文件被调用
MacOSXBundleInfo.plist.in,可以在 CMake 的模块目录中找到。它可以作为自定义模板的有用起点。
Every application bundle must have at least an Info.plist file and a main
executable (MyApp in the directory structure examples above). By default,
CMake will provide a basic Info.plist file from a template file shipped with
CMake itself. In most cases, however, projects will want to provide their own
Info.plist so that they have full control over the app configuration. When
the app uses storyboard or interface builder files, providing a custom
Info.plist is pretty much required so that the relevant key entries like
NSMainStoryboardFile are present. The MACOSX_BUNDLE_INFO_PLIST target
property can be set to the name of a file to use as the Info.plist template
(for all Apple platforms, not just macOS). The default template file is called
MacOSXBundleInfo.plist.in and can be found in CMake’s modules directory. It
may serve as a useful starting point for custom templates.
无论目标是使用默认Info.plist模板还是项目提供的模板,CMake 都会将模板文件复制到应用程序包中,并在此过程中执行一些特定的替换。在模板文件中,
如果目标属性是下表中的属性之一,则表单的任何内容都${XXX}将替换为目标属性的值。这些属性中的每一个都映射到默认
文件中的特定键,因此如果项目提供自己的模板文件并使用这些变量,它通常应该遵循相同的映射。XXXXXXInfo.plist
Regardless of whether a target uses the default Info.plist template or one
provided by the project, CMake will copy the template file into the app bundle,
performing some specific substitutions along the way. In the template file, any
content of the form ${XXX} will be substituted by the value of the XXX
target property if XXX is one of the properties in the table below. Each of
these properties is mapped to a particular key in the default Info.plist
file, so if the project provides its own template file and uses these
variables, it should generally follow the same mapping.
| 财产 | Info.plist键 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
下表提供了上述属性的示例:
The following table provides examples of the above properties:
| 财产 | 例子 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
见下文 see below |
|
见下文 see below |
|
见下文 see below |
Apple 不再将文档CFBundleLongVersionString作为密钥之一
Info.plist,因此项目可能会选择不提供它。他们的文档还指出NSHumanReadableCopyright已替换
CFBundleGetInfoString和CFBundleIconFile已弃用,并建议使用CFBundleIconFiles或CFBundleIcons代替。
CFBundleIconFile如果其他选项均未设置,则仍会受到尊重。
Apple no longer documents CFBundleLongVersionString as one of the
Info.plist keys, so projects may choose to not provide it. Their
documentation also states that NSHumanReadableCopyright has replaced
CFBundleGetInfoString and that CFBundleIconFile is deprecated and
recommends using CFBundleIconFiles or CFBundleIcons instead.
CFBundleIconFile is still honored if neither of the other alternatives is
set.
如果定义了多个应用程序目标,项目可以设置与上表中的属性名称完全相同的变量,并且这些变量将用于初始化目标属性。请注意,这与通常的 CMake 约定不同,变量CMAKE_…在充当默认值的目标属性之前具有前缀。
If multiple app targets are being defined, a project may set variables with
exactly the same names as the properties in the above table and the variables
will be used to initialize the target properties. Note that this differs from
the usual CMake convention of variables having a CMAKE_… prefix before the
target property they act as defaults for.
当项目提供自己的Info.plist模板文件时,不需要使用上述目标属性。它对于硬编码值来说是完全有效的。但请注意,CFBundleVersion和
CFBundleShortVersionString可能需要从文件中指定的版本详细信息派生CMakeLists.txt,因此设置这些 via
MACOSX_BUNDLE_BUNDLE_VERSION和MACOSX_BUNDLE_SHORT_VERSION_STRING
替换可能仍然是最方便的方法。
When a project provides its own Info.plist template file, it is not required
to make any use of the above target properties. It is perfectly valid to
hard-code values instead. Note, however, that CFBundleVersion and
CFBundleShortVersionString may need to be derived from version details
specified within the CMakeLists.txt files, so setting these via
MACOSX_BUNDLE_BUNDLE_VERSION and MACOSX_BUNDLE_SHORT_VERSION_STRING
substitutions may still be the most convenient approach.
Apple 对版本号的要求随着时间的推移而不断发展,
major.minor.patchCFBundleShortVersionString现在是和 的文档格式
CFBundleVersion。在 的情况下CFBundleVersion,仅需要
主要部分 -次要版本和补丁版本组件是可选的。一种策略是CFBundleVersion从持续集成系统设置作业编号,这将确保
CFBundleShortVersionString预发布阶段的后续构建将具有唯一且不断增加的构建编号。当使用相同的 更新已安装的应用程序时
CFBundleShortVersionString,这将确保操作系统将最新版本视为较新版本。
The Apple requirements around the version numbers have evolved over time, with
major.minor.patch now the documented format for both
CFBundleShortVersionString and CFBundleVersion.
In the case of CFBundleVersion, only the major part is required — the
minor and patch version components are optional.
One strategy is to set CFBundleVersion to the job number from a continuous
integration system, which will ensure that subsequent builds for the same
CFBundleShortVersionString during the pre-release phase will have unique and
increasing build numbers.
When updating an already installed app with the same
CFBundleShortVersionString, this will ensure the operating system sees the
latest build as a newer version.
以下 CMake 代码和示例Info.plist文件显示了提供满足 Apple 要求的版本号的一种方法:
The following CMake code and sample Info.plist file show one way to provide
version numbers that meet Apple’s requirements:
# CI systems typically provide some form of job ID as an
# environment variable. This example works for gitlab, but
# other CI systems are likely to be similar. When not run
# under a CI system, this will leave BUILD_VERSION unset.
set(BUILD_VERSION $ENV{CI_JOB_ID})
if(BUILD_VERSION STREQUAL "")
# This is a local build, not through CI system
set(BUILD_VERSION 0)
endif()
add_executable(MyApp MACOSX_BUNDLE ...)
set_target_properties(MyApp PROPERTIES
MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}"
MACOSX_BUNDLE_INFO_PLIST
"${CMAKE_CURRENT_SOURCE_DIR}/Info.plist"
)# CI systems typically provide some form of job ID as an
# environment variable. This example works for gitlab, but
# other CI systems are likely to be similar. When not run
# under a CI system, this will leave BUILD_VERSION unset.
set(BUILD_VERSION $ENV{CI_JOB_ID})
if(BUILD_VERSION STREQUAL "")
# This is a local build, not through CI system
set(BUILD_VERSION 0)
endif()
add_executable(MyApp MACOSX_BUNDLE ...)
set_target_properties(MyApp PROPERTIES
MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}"
MACOSX_BUNDLE_INFO_PLIST
"${CMAKE_CURRENT_SOURCE_DIR}/Info.plist"
)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundleDevelopmentRegion</key><string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSMainStoryboardFile</key><string>Main</string>
<key>NSPrincipalClass</key><string>NSApplication</string>
</dict></plist><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundleDevelopmentRegion</key><string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSMainStoryboardFile</key><string>Main</string>
<key>NSPrincipalClass</key><string>NSApplication</string>
</dict></plist>
在上面的示例Info.plist文件中,请注意如何使用语法将某些字段值作为 CMake 变量提供${},而CFBundleExecutable和 则
LSMinimumSystemVersion使用 Xcode 变量替换语法提供
$()。这两个字段将由 Xcode 本身根据项目文件中提供的其他信息和正在构建的方案进行填充。对于 macOS 应用程序,该值LSMinimumSystemVersion将从变量派生
,或者从iOS 的目标属性派生(但请注意,CMake 3.11 及更高版本可用于所有平台,请参阅下面第 23.5 节“构建设置”中的讨论) )。如果更方便的话,项目可以直接在文件中硬编码一个值。CMAKE_OSX_DEPLOYMENT_TARGETXCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGETCMAKE_OSX_DEPLOYMENT_TARGETInfo.plist
In the example Info.plist file above, note how some field values are provided
as CMake variables using the ${} syntax, but the CFBundleExecutable and
LSMinimumSystemVersion are provided using Xcode variable substitution syntax
$() instead.
These two fields will be populated by Xcode itself based on other information
provided in the project file and the scheme being built.
The value for LSMinimumSystemVersion will be derived from the
CMAKE_OSX_DEPLOYMENT_TARGET variable in the case of a macOS app, or
from the XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET target property for iOS
(but note that CMake 3.11 and later can use CMAKE_OSX_DEPLOYMENT_TARGET for
all platforms, see discussion in Section 23.5, “Build Settings” further below).
Projects can instead hard-code a value directly in the Info.plist file, if
that is more convenient.
使用 Storyboard 或界面构建器文件时,该Info.plist文件应包含键之一NSMainStoryboardFile,NSMainNibFile或
UIMainStoryboardFile(有关这些键的含义和适当使用的详细信息,请参阅 Apple 文档)。此类条目告诉操作系统在启动应用程序时要使用哪个 UI 元素。在上面的示例中,该NSMainStoryboardFile字段的值为Main,它指定Main.storyboard应用程序启动时将使用 UI。这将在下一节中进一步讨论。
When using Storyboard or interface builder files, the Info.plist file should
contain one of the keys NSMainStoryboardFile, NSMainNibFile or
UIMainStoryboardFile (see the Apple documentation for details on the meaning
and appropriate use of these keys).
Such an entry tells the operating system which UI element to use when
launching the app.
In the above example, the NSMainStoryboardFile field has the value Main,
which specifies that a Main.storyboard UI will be used when the app starts.
This is discussed further in the next section.
定义适当的Info.plist文件后,可以将注意力转向要编译并链接到捆绑包中的源文件。除了常见的 C/C++ 源文件外,Apple 平台还支持 Objective C/C++ 源文件。这些通常具有.m或文件后缀,并且可以像普通 C/C++ 文件一样在和命令.mm中列为源(有关定义目标源的更多信息,请参阅第 29.5 节“定义目标” )。大多数 CMake 生成器都会识别这些文件后缀并相应地编译文件,而不仅仅是 Xcode 生成器。从 CMake 3.16 开始,和被识别为不同的语言,并且可以与和分开启用。它们还支持自己的特定于语言的 CMake 变量集
,例如 、 等等。add_executable()target_sources()OBJCOBJCXXCCXXCMAKE_OBJC_FLAGSCMAKE_OBJCXX_STANDARD
With an appropriate Info.plist file defined, attention can be turned to the
source files to be compiled and linked into the bundle. In addition to the
usual C/C++ sources, Apple platforms also support Objective C/C++ source
files. These typically have a .m or .mm file suffix and can be listed as
sources in add_executable() and target_sources() commands just like
ordinary C/C++ files (see Section 29.5, “Defining Targets” for more on defining
target sources). Most of CMake’s generators will recognize these file suffixes
and compile the files appropriately, not just the Xcode generator.
Starting from CMake 3.16, OBJC and OBJCXX are recognized as distinct
languages and can be enabled separately from C and CXX.
They also support their own language-specific sets of CMake variables like
CMAKE_OBJC_FLAGS, CMAKE_OBJCXX_STANDARD and so on.
Apple 平台特有的另一组源文件是用于定义用户界面的文件。故事板或界面构建器文件类似于源,但它们需要一些额外的处理才能将它们编译为资源并将编译结果放在应用程序包中的适当位置。只有 Xcode 生成器实现此自动编译并复制到适当的位置,因此当应用程序包包含这些文件时,通常不建议使用 Makefile 或 Ninja 生成器。
Another group of source files unique to Apple platforms are those used to define the user interface. Storyboard or interface builder files are like sources, but they require some additional handling to compile them as resources and put the compiled result in the appropriate place in the app bundle. Only the Xcode generator implements this automatic compilation and copying to the appropriate location, so the use of Makefile or Ninja generators is generally not recommended when an app bundle has these files.
add_executable()故事板和界面生成器源需要在或中列为源
target_sources()。为了让它们自动编译并复制到包中的适当位置,还需要将它们列在RESOURCE目标属性中或将其MACOSX_PACKAGE_LOCATION源属性设置为
Resources. 这两种方法之间的主要区别在于安装目标后会发生什么(第 26 章“安装”专门讨论了这个广泛的主题)。目标属性中列出的任何文件都RESOURCE将被复制到
命令RESOURCE中指定的目标install(TARGET),无论源文件是什么类型。对于需要编译的源文件来说,这通常是不可取的,但适用于要添加到应用程序包中的其他任意文件。另一方面,如果 Xcode 将其MACOSX_PACKAGE_LOCATION设置Resources为应编译的源文件,则不会安装它们。将安装所有其他类型的源文件。
第 23.8.3 节“组合设备和模拟器二进制文件”讨论了这种行为差异特别相关的特定场景。
Storyboard and interface builder sources need to be listed as sources in
add_executable() or target_sources().
To get them to be automatically compiled and copied to the appropriate location
in the bundle, they also need to be listed in the RESOURCE target
property or have their MACOSX_PACKAGE_LOCATION source property set to
Resources.
The main difference between these two approaches is what happens if the target
is installed (Chapter 26, Installing is dedicated to this broad topic).
Any file listed in the RESOURCE target property will be copied to the
RESOURCE destination given in the install(TARGET) command, regardless of
what type of source file it is.
This is usually undesirable for source files that need to be compiled, but is
suitable for other arbitrary files to be added to the app bundle.
On the other hand, source files that have their MACOSX_PACKAGE_LOCATION set
to Resources will not be installed if Xcode recognizes them as source files
that should be compiled.
All other types of source files will be installed.
Section 23.8.3, “Combined Device And Simulator Binaries” discusses a specific scenario where
this difference in behavior is particularly relevant.
set(compileRes
Base.lproj/Main.storyboard
Base.lproj/LaunchScreen.storyboard
Assets.xcassets ①
)
set(directCopyRes
defaultConfig.xml
inventoryDb.dat
)
add_executable(MyApp MACOSX_BUNDLE
AppDelegate.m
AppDelegate.h
ViewController.m
ViewController.h
main.m
${compileRes}
${directCopyRes}
)
set_target_properties(MyApp PROPERTIES
RESOURCE "${directCopyRes}" ②
)
set_source_files_properties(${compileRes} PROPERTIES
MACOSX_PACKAGE_LOCATION Resources
)set(compileRes
Base.lproj/Main.storyboard
Base.lproj/LaunchScreen.storyboard
Assets.xcassets ①
)
set(directCopyRes
defaultConfig.xml
inventoryDb.dat
)
add_executable(MyApp MACOSX_BUNDLE
AppDelegate.m
AppDelegate.h
ViewController.m
ViewController.h
main.m
${compileRes}
${directCopyRes}
)
set_target_properties(MyApp PROPERTIES
RESOURCE "${directCopyRes}" ②
)
set_source_files_properties(${compileRes} PROPERTIES
MACOSX_PACKAGE_LOCATION Resources
)
RESOURCE属性的单个值传递。RESOURCE property.MACOSX_PACKAGE_LOCATION当设置为以 开头的路径时,适用一种特殊情况Resources,并且目标是为 iOS、tvOS 或 watchOS 构建的。由于这些平台的应用程序包使用扁平结构,因此 CMake 将剥离Resources路径的一部分。在 CMake 3.9 之前,此行为的实现不正确,并且并不总是能够将文件放入所需位置。
A special case applies when setting the MACOSX_PACKAGE_LOCATION to a path
starting with Resources and the target is being built for iOS, tvOS or
watchOS.
Because app bundles for these platforms use a flattened structure, CMake will
strip off the Resources part of the path.
Prior to CMake 3.9, this behavior was implemented incorrectly and it was not
always possible to get a file into the desired location.
如果需要将源文件复制到具有某种目录结构的应用程序包中,或者需要将它们放置在目录以外的其他位置Resources,则MACOSX_PACKAGE_LOCATION必须使用该方法。例如:
For cases where source files need to be copied into the app bundle with some
sort of directory structure, or where they need to be located somewhere other
than the Resources directory, the MACOSX_PACKAGE_LOCATION method must be
used.
For example:
set(sharedRes defaultConfig.xml defaultInventoryDb.dat)
add_executable(MyApp MACOSX_BUNDLE ... ${sharedRes})
set_source_files_properties(${sharedRes} PROPERTIES
MACOSX_PACKAGE_LOCATION SharedSupport/defaults
)set(sharedRes defaultConfig.xml defaultInventoryDb.dat)
add_executable(MyApp MACOSX_BUNDLE ... ${sharedRes})
set_source_files_properties(${sharedRes} PROPERTIES
MACOSX_PACKAGE_LOCATION SharedSupport/defaults
)
框架与应用程序包有一些相似之处,但它们也有许多独特的功能。框架包含一个主库,但与应用程序包不同,在 macOS 上可能有多个版本的库。除了常见的资源之外,框架还支持标头,对于 macOS,资源和标头都是特定于版本的。
Frameworks share some similarities with application bundles, but they also have a number of unique features. A framework contains a main library, but unlike an application bundle, on macOS there may be multiple versions of the library. In addition to the usual resources, frameworks also support headers and in the case of macOS, both the resources and headers are version-specific.
macOS 框架结构的典型示例如下所示。
A typical example of the macOS framework structure looks something like this.
框架的顶层始终有一个以 结尾的名称.framework
,并且通常该顶层目录中唯一的非符号链接内容是子目录Versions(伞框架是例外,但这些超出了此处考虑的框架支持范围)。该级别的其他所有内容通常是当前版本子目录中某些内容的符号链接。
The top level of the framework always has a name that ends with .framework
and typically the only non-symlinked contents in that top level directory is
the Versions subdirectory (umbrella frameworks being the exception, but those
are outside the scope of framework support being considered here). Everything
else at that level is usually a symlink to something in the current version’s
subdirectory.
在该Versions目录中,库的每个版本都有自己的子目录,其名称就是版本。在大多数情况下,这些目录名称只是A、B等。使用数字版本是另一个常见约定,它与共享库在其他平台上的版本控制方式更加一致。无论版本控制的风格如何,名为 的符号链接Current
都会指向最新版本,并且它的作用类似于框架的默认版本。每个版本都应该有一个Resources至少包含一个Info.plist文件的目录,该文件提供有关该特定版本的配置详细信息(下面进一步讨论)。还将有一个库(通常是共享库,但它可以是静态的),并且通常
Headers还有潜在的PrivateHeaders子目录。
Within the Versions directory, each version of the library gets its own
subdirectory whose name is the version. In most cases, these directory names
are just A, B, etc. Use of numeric versions is another common convention,
which aligns more closely with how shared libraries are versioned on other
platforms. Regardless of the style of versioning, a symlink called Current
points to the most recent version and it acts like a default version for the
framework. Each version is expected to have a Resources directory that
contains at least an Info.plist file, which provides configuration details
about that particular version (discussed further below). There will also be a
library (which is usually a shared library, but it can be static) and often
Headers and potentially PrivateHeaders subdirectories as well.
相比之下,iOS、tvOS 和 watchOS 上的结构是扁平化的,通常不支持版本:
In comparison, the structure on iOS, tvOS and watchOS is flattened and does not typically support versions:
CMake 支持创建框架(仅适用于 macOS 的单一版本),并提供处理版本详细信息的功能。还支持Info.plist采用与应用程序包类似的方法的文件。第一步是以通常的方式定义一个库,然后通过将FRAMEWORKtarget 属性设置为 true 将其标记为框架。在 CMake 3.15 或更高版本中,FRAMEWORK目标属性使用变量的值进行初始化CMAKE_FRAMEWORK,而对于 3.14 及更早版本,目标属性最初未设置。大多数框架都定义为共享库,但从 CMake 3.8 开始,静态库也可以构建为框架。FRAMEWORK在非 Apple 平台上,目标属性被忽略。
CMake supports the creation of frameworks (single-version only in the case of
macOS) and it provides features for handling the version details. There is also
support for Info.plist files which follows a similar approach to that used
for application bundles. The first step is to define a library in the usual way
and then mark it as a framework by setting the FRAMEWORK target property
to true.
With CMake 3.15 or later, the FRAMEWORK target property is initialized with
the value of the CMAKE_FRAMEWORK variable, while for 3.14 and earlier,
the target property is initially unset.
Most frameworks are defined as shared libraries, but as of CMake 3.8, static
libraries can also be built as frameworks. The FRAMEWORK target property is
ignored on non-Apple platforms.
仅对于 macOS,可以使用 target 属性指定框架版本
,或者如果省略,
将设置FRAMEWORK_VERSION默认版本。A如果设置了该属性,非 macOS Apple 平台将忽略该FRAMEWORK_VERSION属性,从而在为这些平台创建框架时生成与 Xcode 生成的相同的扁平化、无版本化的框架结构。
For macOS only, the framework version can be specified using the
FRAMEWORK_VERSION target property, or if omitted a default version of A
will be set.
Non-macOS Apple platforms will ignore the FRAMEWORK_VERSION property if it is
set, producing the same flattened, unversioned framework structure produced by
Xcode when it creates frameworks for these platforms.
add_library(MyFramework SHARED foo.cpp)
set_target_properties(MyFramework PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION 5
)add_library(MyFramework SHARED foo.cpp)
set_target_properties(MyFramework PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION 5
)
文件Info.plist模板的指定方式与应用程序包相同,除了调用目标属性MACOSX_FRAMEWORK_INFO_PLIST
(所有 Apple 平台都支持这,而不仅仅是 macOS):
The Info.plist file template is specified in the same way as for application
bundles, except the target property is called MACOSX_FRAMEWORK_INFO_PLIST
(this is supported for all Apple platforms, not just macOS):
set_target_properties(MyFramework PROPERTIES
MACOSX_FRAMEWORK_INFO_PLIST
"${CMAKE_CURRENT_SOURCE_DIR}/Info.plist"
)set_target_properties(MyFramework PROPERTIES
MACOSX_FRAMEWORK_INFO_PLIST
"${CMAKE_CURRENT_SOURCE_DIR}/Info.plist"
)
对于应用程序包,如果Info.plist未显式提供框架文件,则会自动生成默认框架文件。无论项目提供自己的Info.plist还是依赖默认值,CMake 在将其复制到框架时都会执行与应用程序包类似的替换。以下目标属性将在
Info.plist文件引用的地方被替换(
Info.plist还列出了文件中预期的关联键名称):
As for application bundles, if a framework Info.plist file is not explicitly
provided, a default one is automatically generated. Whether the project
provides its own Info.plist or it relies on the default, CMake will perform a
similar substitution as for application bundles when copying it into the
framework. The following target properties will be substituted where the
Info.plist file refers to them (the expected associated key name in the
Info.plist file is also listed):
| 财产 | Info.plist键 |
|---|---|
|
|
|
|
|
|
|
|
与应用程序包不同,默认框架Info.plist文件在许多情况下可能就足够了,因此项目通常只需设置上述四个目标属性并让 CMake 提供适当的Info.plist
文件。
Unlike for application bundles, the default framework Info.plist file is
likely to be sufficient in many cases, so the project can usually just set the
above four target properties and let CMake provide an appropriate Info.plist
file.
框架通常包含与框架的库关联的标头。这使得该框架可以被视为一个独立的捆绑包,其他软件可以基于该捆绑包进行构建。框架标头分为公共和私有组,只有公共标头旨在由使用项目直接包含或导入。私有标头通常需要作为公共标头的内部实现细节,而框架通常根本不包含任何私有标头。
Frameworks often contain the headers associated with the framework’s library. This allows the framework to be treated as a self-contained bundle which other software can build against. Framework headers are separated into public and private groups, with only public headers intended to be directly included or imported by consuming projects. The private headers are usually needed as internal implementation details by the public headers and frameworks often do not include any private headers at all.
PUBLIC_HEADERCMake 支持分别使用和target 属性指定公共和私有标头集
PRIVATE_HEADER。这两个属性都包含头文件列表,并且所有提到的文件也必须明确列为目标源,否则它们不会被复制到框架中。中列出的文件PUBLIC_HEADER将被复制到框架的Headers
目录中,并删除路径,而 中列出的文件PRIVATE_HEADER将被复制到该PrivateHeaders目录中,并再次删除任何路径。如果需要保留路径,则不能使用这些目标属性,并且必须使用诸如第23.2.3 节“源、资源和其他文件”MACOSX_PACKAGE_LOCATION
中所述的via 等技术来添加标头。
CMake supports specifying the set of public and private headers with the
PUBLIC_HEADER and PRIVATE_HEADER target properties respectively.
Both properties contain a list of header files and all files mentioned must
also be explicitly listed as sources for the target or they won’t be copied
into the framework.
Files listed in PUBLIC_HEADER will be copied into the framework’s Headers
directory with paths stripped, while files listed in PRIVATE_HEADER will be
copied into the PrivateHeaders directory, again with any paths stripped.
If paths need to be preserved, these target properties cannot be used and the
headers have to be added using techniques such as via MACOSX_PACKAGE_LOCATION
as described in Section 23.2.3, “Sources, Resources And Other Files”.
add_library(MyFramework SHARED
foo.cpp
foo.h
foo_privateA.h
nested/foo_privateB.h
)
set_target_properties(MyFramework PROPERTIES
FRAMEWORK TRUE
PUBLIC_HEADER foo.h
PRIVATE_HEADER "foo_privateA.h;nested/foo_privateB.h"
)add_library(MyFramework SHARED
foo.cpp
foo.h
foo_privateA.h
nested/foo_privateB.h
)
set_target_properties(MyFramework PROPERTIES
FRAMEWORK TRUE
PUBLIC_HEADER foo.h
PRIVATE_HEADER "foo_privateA.h;nested/foo_privateB.h"
)
上面的示例将在 macOS 上产生以下结构:
The above example would result in the following structure on macOS:
iOS 上的相同示例将导致更扁平的结构:
The same example on iOS would result in a more flattened structure:
请注意,在非 Apple 平台上安装目标时也会使用PUBLIC_HEADER和target 属性(请参阅第 26.2.3 节“Apple 特定目标”)。PRIVATE_HEADER
Note that the PUBLIC_HEADER and PRIVATE_HEADER target properties are also
used when installing targets on non-Apple platforms
(see Section 26.2.3, “Apple-specific Targets”).
除了应用程序捆绑包和框架之外,Apple 还支持 macOS 的可加载捆绑包。它们通常用作插件或提供运行时可能支持或不支持的可选功能。可加载包的结构与应用程序包的结构相同,但顶级目录通常具有扩展名.bundle或.plugin(技术上允许任何扩展名)。MODULECMake 支持通过库类型和目标属性创建可加载包BUNDLE。默认情况下,可加载包将被赋予扩展名bundle,但这可以用BUNDLE_EXTENSION目标属性覆盖。
In addition to application bundles and frameworks, Apple also supports loadable
bundles for macOS. These are often used as plugins or to provide optional
features which might or might not be supported at run time. The structure of a
loadable bundle is the same as that of an application bundle, but the top level
directory usually has the extension .bundle or .plugin (any extension is
technically permitted). CMake supports the creation of loadable bundles through
the MODULE library type and the BUNDLE target property.
By default, loadable bundles will be given the extension bundle, but this
can be overridden with the BUNDLE_EXTENSION target property.
add_library(MyBundle MODULE ...)
set_target_properties(MyBundle PROPERTIES
BUNDLE TRUE
BUNDLE_EXTENSION plugin
)add_library(MyBundle MODULE ...)
set_target_properties(MyBundle PROPERTIES
BUNDLE TRUE
BUNDLE_EXTENSION plugin
)
与应用程序包相关的所有目标属性也可用于可加载包。
All of the target properties relating to application bundles can also be used for loadable bundles.
在为 Apple 平台构建项目时,许多属性一起定义要构建的平台并指定最低平台版本要求。与其他 CMake 生成器类型不同,Xcode 生成器允许开发人员在构建时指定其中许多生成器,而不是在配置时已知,这一特性对于新手和有经验的人来说可能是正确处理的更困难的方面之一CMake 用户都一样。
When building a project for Apple platforms, a number of properties work together to define what platform to build for and to specify minimum platform version requirements. Unlike other CMake generator types, the Xcode generator allows a number of these to be specified at build time by the developer rather than being known at configure time, a characteristic which can be one of the more difficult aspects to handle correctly for both new and experienced CMake users alike.
对于单个配置生成器,目标设备在配置时是已知的,但对于 Xcode,某些平台同时支持设备和设备模拟器。此外,其中一些设备具有多种架构。对于 iOS,这可能意味着最多五种不同的目标平台组合。为了允许开发人员在构建时在不同的设备目标和 SDK 之间切换,CMake 项目必须小心,不要过度指定或错误地指定这些细节。
For single configuration generators, the target device is known exactly at configure time, but for Xcode, some platforms support both the device and device simulators. Furthermore, some of these devices have multiple architectures. In the case of iOS, this can mean up to five different target platform combinations. In order to allow developers to switch between different device targets and SDKs at build time, CMake projects must be careful to not over-specify or incorrectly specify these details.
选择适用于 iOS、tvOS 和 watchOS 的 SDK 是许多在线示例表现出相当复杂性的一个领域,并且通常会导致开发人员无法在不重新运行 CMake 的情况下在设备和模拟器版本之间切换。然而,对于最新版本的 CMake 和 Xcode,指定 SDK 应该相对简单。CMake 3.14 中添加的改进尤其如此,可能唯一需要的设置是将变量设置CMAKE_SYSTEM_NAME为
iOS,tvOS或watchOS。甚至可能不需要工具链文件,CMAKE_SYSTEM_NAME可以直接在命令行上设置cmake,尽管根据 Xcode 版本可能仍然需要一些解决方法(请参阅第 23.5.4 节,“编译器测试解决方法”)。使用 CMake 3.13 或更早版本,可以通过将变量设置为、或
CMAKE_OSX_SYSROOT之一来实现类似的结果
,尽管对 tvOS 和 watchOS 的支持不太完整并且应被视为不可靠。iphoneosappletvoswatchos
The selection of the SDK for iOS, tvOS and watchOS is one area where many
online examples exhibit considerable complexity and often result in locking the
developer out of the ability to switch between device and simulator builds
without re-running CMake.
With recent versions of CMake and Xcode, however, specifying the SDK should be
relatively straightforward.
This is especially so with improvements added in CMake 3.14, with potentially
the only required setup being to set the CMAKE_SYSTEM_NAME variable to
iOS, tvOS or watchOS.
A toolchain file may not even be needed, CMAKE_SYSTEM_NAME can be set
directly on the cmake command line, although some workarounds may still be
needed depending on the Xcode version (see Section 23.5.4, “Compiler Test Workarounds”).
With CMake 3.13 or earlier, a similar result can be achieved by setting the
CMAKE_OSX_SYSROOT variable to one of iphoneos, appletvos or
watchos, although the support for tvOS and watchOS is less complete and
should be considered unreliable.
使用这两种方法中的任何一种,Xcode 都会为该平台选择最新的 SDK,并且允许在设备和模拟器版本之间切换,而无需重新运行 CMake。此外,Xcode 可以根据所选的 SDK 自动填充支持的架构集,因此项目不需要直接指定它们(有关此主题的更多信息,请参阅第 23.8.2 节“Intel 和 Apple Silicon 二进制文件” )。这使开发人员可以最大程度地控制他们想要构建的内容,而无需重新运行 CMake。
With either of these methods, Xcode will choose the latest SDK for that platform and it will allow switching between device and simulator builds without having to re-run CMake. Furthermore, Xcode can automatically populate the set of supported architectures based on the chosen SDK, so the project shouldn’t need to specify them directly (see Section 23.8.2, “Intel And Apple Silicon Binaries” for more on this topic). This gives the developer the most control over what they want to build without having to re-run CMake.
-sdk可以提供在构建时选择设备或模拟器的选项。例如,如果CMAKE_SYSTEM_NAME设置为iOS,则可以像这样构建模拟器:
The -sdk option can be given to choose the device or simulator at build time.
For example, if CMAKE_SYSTEM_NAME was set to iOS, then building for the
simulator could be done like so:
xcodebuild-sdk iphone模拟器
xcodebuild -sdk iphonesimulator
如果好奇的话,可以通过运行以下命令来获取可用的 SDK:
For the curious, the available SDKs can be obtained by running the following command:
xcodebuild-showsdks
xcodebuild -showsdks
对于任一设置方法CMAKE_OSX_SYSROOT或CMAKE_SYSTEM_NAME所需平台,项目都会将其部署目标设置为 SDK 或主机系统默认支持的最新目标。这通常是不可取的,因为项目通常希望与特定的最低操作系统版本保持兼容。
For either method of setting CMAKE_OSX_SYSROOT or CMAKE_SYSTEM_NAME to the
desired platform, the project will have its deployment target set to the most
recent one the SDK or host system supports by default.
This will often be undesirable, since projects typically want to remain
compatible with a specific minimum OS version.
对于 macOS,OSX_DEPLOYMENT_TARGET目标属性控制目标将支持的最低 macOS 版本。可以使用变量为此目标属性指定默认值CMAKE_OSX_DEPLOYMENT_TARGET
,但这必须在project()调用第一个命令之前设置。此外,CMAKE_OSX_DEPLOYMENT_TARGET如果直接在顶级CMakeLists.txt文件中设置,则需要是缓存变量,否则它将被命令执行的编译器检查覆盖project()。
For macOS, the OSX_DEPLOYMENT_TARGET target property controls
the minimum macOS version the target will support. A default value can be
specified for this target property using the CMAKE_OSX_DEPLOYMENT_TARGET
variable, but this must be set before the first project() command is called.
Furthermore, CMAKE_OSX_DEPLOYMENT_TARGET needs to be a cache variable if it
is being set directly in the top level CMakeLists.txt file, otherwise it will
be overwritten by the compiler checks performed by the project() command.
另一种策略是使用工具链文件并
CMAKE_OSX_DEPLOYMENT_TARGET在其中进行设置,但是在 macOS 构建中使用工具链文件相当罕见,并且此变量应该由项目而不是开发人员定义。另一种方法是
CMAKE_OSX_DEPLOYMENT_TARGET在cmake命令行上设置缓存变量,但这也让开发人员有责任记住设置它并提供正确的值,从而使其吸引力降低。
An alternative strategy is to use a toolchain file and set
CMAKE_OSX_DEPLOYMENT_TARGET within it, but the use of toolchain files for
macOS builds would be rather uncommon and this variable is something that the
project should define, not the developer. One more approach would be to set the
CMAKE_OSX_DEPLOYMENT_TARGET cache variable on the cmake command line, but
this also puts the responsibility on the developer to remember to set it and to
provide the correct value, making it less attractive.
在 CMake 3.11 之前,当针对 macOS 以外的平台时,该
CMAKE_OSX_DEPLOYMENT_TARGET变量不起作用。要控制 CMake 3.11 之前的 iOS 的最低部署目标版本,请改用
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGETtarget 属性。可以使用该变量设置此目标属性的默认值,
CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET与 macOS 不同的是,可以在第一次调用后设置此变量project()。从 CMake 3.11 开始,CMAKE_OSX_DEPLOYMENT_TARGET可用于定义任何 Apple 平台的最低部署目标版本,而不仅仅是 macOS。如果目标最终同时定义了OSX_DEPLOYMENT_TARGET和
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET目标属性,则在使用 Xcode 生成器时后者优先。
Prior to CMake 3.11, when targeting platforms other than macOS, the
CMAKE_OSX_DEPLOYMENT_TARGET variable had no effect. To control the minimum
deployment target version for iOS before CMake 3.11, use the
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET target property instead. A
default value for this target property can be set using the
CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET variable and unlike for
macOS, this variable can be set after the first project() call. From CMake
3.11 onward, CMAKE_OSX_DEPLOYMENT_TARGET can be used to define the
minimum deployment target version for any of the Apple platforms, not just
macOS. If a target ends up with both OSX_DEPLOYMENT_TARGET and
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET target properties defined, the
latter takes precedence when using the Xcode generator.
cmake_minimum_required(VERSION 3.9)
# Set the deployment target for macOS with any CMake
# version or all Apple platforms when using CMake 3.11 or
# later. Must be set before first call to project() and it
# must be a cache variable.
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.11 CACHE STRING "")
project(AppleProject)cmake_minimum_required(VERSION 3.9)
# Set the deployment target for macOS with any CMake
# version or all Apple platforms when using CMake 3.11 or
# later. Must be set before first call to project() and it
# must be a cache variable.
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.11 CACHE STRING "")
project(AppleProject)
# Set the deployment target for iOS with any CMake version.
# Set defaults for all targets in the project
set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 9.0)
# Build an app with the deployment target explicitly set
add_executable(MyApp MACOSX_BUNDLE ...)
set_target_properties(MyApp PROPERTIES
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 10.0
)# Set the deployment target for iOS with any CMake version.
# Set defaults for all targets in the project
set(CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 9.0)
# Build an app with the deployment target explicitly set
add_executable(MyApp MACOSX_BUNDLE ...)
set_target_properties(MyApp PROPERTIES
XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 10.0
)
就 iOS 而言,项目也可能希望指定目标设备系列。Apple 表示在属性中指定整数值的设备TARGETED_DEVICE_FAMILY。对于 iOS,iPhone 的有效值为 1(技术上 iPod touch 也是如此),iPad 的有效值为 2。如果应用程序应同时支持 iPhone 和 iPad,则指定两个值,并用逗号分隔。如果未设置此属性,则默认为 1。Xcode 将使用此值
自动UIDeviceFamily在应用程序文件中添加条目,因此请避免在项目提供的Info.plist任何自定义中设置此条目。Info.plist
In the case of iOS, projects will also likely want to specify the device
families being targeted. Apple denotes devices with integer values specified in
the TARGETED_DEVICE_FAMILY attribute. For iOS, valid values are 1 for iPhone
(and technically iPod touch too) or 2 for iPad. If the app should support both
iPhone and iPad, then specify both values separated by a comma. If this
attribute is not set, it will default to 1. Xcode will use this value to add a
UIDeviceFamily entry in the app’s Info.plist file automatically, so avoid
setting this entry in any custom Info.plist supplied by the project.
# An iOS app that supports only iPad
add_executable(MyiPadApp MACOSX_BUNDLE ...)
set_target_properties(MyiPadApp PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY 2
)
# An iOS app that supports both iPhone and iPad
add_executable(RunEverywhereApp MACOSX_BUNDLE ...)
set_target_properties(RunEverywhereApp PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY 1,2
)# An iOS app that supports only iPad
add_executable(MyiPadApp MACOSX_BUNDLE ...)
set_target_properties(MyiPadApp PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY 2
)
# An iOS app that supports both iPhone and iPad
add_executable(RunEverywhereApp MACOSX_BUNDLE ...)
set_target_properties(RunEverywhereApp PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY 1,2
)
当为 macOS 以外的 Apple 平台进行构建时,CMake 执行的编译器检查以及try_compile()项目启动的任何调用都有可能触发各种问题。其中许多问题来自于在编译器检查或try_compile()调用期间启用代码签名,但在这些上下文中签名通常是不必要也不可取的。避免这些问题的步骤取决于所使用的 Xcode 和 CMake 版本的组合,但在几乎所有情况下,使用工具链文件最容易实现解决方法。
When building for Apple platforms other than macOS, the compiler checks
performed by CMake and any try_compile() invocations initiated by the project
have the potential to trigger a variety of problems.
Many of these problems come from code signing being enabled during compiler
checks or try_compile() invocations, but signing is usually not necessary nor
desirable in those contexts.
The steps to avoid these problems depend on the combination of the Xcode and
CMake versions used, but in almost all cases, the workarounds are most easily
implemented with a toolchain file.
Xcode 11 更改了某些与签名相关的项目选项的处理方式等。为了能够使用适用于 iOS、watchOS 或 tvOS 的 Xcode 11 成功构建,首选 CMake 版本应为 3.18.2 或更高版本。这不需要解决方法即可成功构建。CMake 3.14 或更高版本仍然可以使用(早期版本会失败),但
CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED需要在调用内部将变量设置为 falsetry_compile()以防止签名。不应为构建的主要部分设置此变量,因为主构建的目标可能需要签名。以下工具链文件演示了如何实现这一点:
Xcode 11 changed the handling of certain signing-related project options, among
other things.
To be able to build successfully with Xcode 11 for iOS, watchOS or tvOS,
the preferred CMake version should be 3.18.2 or later.
This requires no workarounds to get a successful build.
CMake 3.14 or later can still be used (earlier versions will fail), but the
CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED variable needs to be set to
false inside try_compile() calls to prevent signing.
This variable should not be set for the main part of the build, since the
main build’s targets will likely need to be signed.
The following toolchain file demonstrates how this can be achieved:
# Any of these platforms can be used with this approach
#set(CMAKE_SYSTEM_NAME watchOS)
#set(CMAKE_SYSTEM_NAME tvOS)
set(CMAKE_SYSTEM_NAME iOS)
# Only disable code signing for try_compile() calls. Leave
# signing details alone for the main part of the build.
get_property(__in_try_compile GLOBAL
PROPERTY IN_TRY_COMPILE
)
if(__in_try_compile)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO)
endif()
unset(__in_try_compile)# Any of these platforms can be used with this approach
#set(CMAKE_SYSTEM_NAME watchOS)
#set(CMAKE_SYSTEM_NAME tvOS)
set(CMAKE_SYSTEM_NAME iOS)
# Only disable code signing for try_compile() calls. Leave
# signing details alone for the main part of the build.
get_property(__in_try_compile GLOBAL
PROPERTY IN_TRY_COMPILE
)
if(__in_try_compile)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO)
endif()
unset(__in_try_compile)
CMake 3.18.2 及更高版本会
CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED在内部调用中自动设置为 false try_compile()
,因此在这种情况下上述内容是不必要的(但也无害)。只CMAKE_SYSTEM_NAME需要为 Xcode/CMake 组合进行设置,这可以作为命令行选项或在工具链文件中完成。
CMake 3.18.2 and later automatically sets
CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED to false inside try_compile()
calls, so the above is not necessary in that case (but also not harmful).
Only the CMAKE_SYSTEM_NAME needs to be set for that Xcode/CMake combination,
which can be done as a command line option or in a toolchain file.
当将 Xcode 10 或更早版本与 CMake 3.14 或更高版本一起使用时,也不需要上述工具链文件,但它不会损害构建。
When using Xcode 10 or earlier with CMake 3.14 or later, the above toolchain file is also not needed, but again it would not harm the build.
当使用 CMake 3.13 或更早版本时,情况更加复杂。以下工具链文件适用于带有 Xcode 10、9 及可能更早版本的 iOS。它至少可以追溯到 CMake 3.7,尽管对于今天构建 iOS 应用程序来说确实应该被认为太旧了:
When using CMake 3.13 or earlier, the situation is more complex. The following toolchain file works for iOS with Xcode 10, 9 and potentially earlier. It works at least as far back as CMake 3.7, although that should really be considered too old for building iOS apps today:
set(CMAKE_OSX_SYSROOT iphoneos)
if(NOT DEFINED CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
endif()
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)set(CMAKE_OSX_SYSROOT iphoneos)
if(NOT DEFINED CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
endif()
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
上述内容阻止了代码检查和try_compile()调用中的代码签名,但仍然在主构建中保持代码签名身份不变(当提供这些详细信息时)。设置CMAKE_TRY_COMPILE_TARGET_TYPE可防止检查尝试构建捆绑包,这将需要额外的信息,例如捆绑包 ID。也可以使用其他选项组合,但以上是最简单的。对于 tvOS 和 watchOS,上述变体可能适用于 3.14 之前的 CMake 版本,但仅在 CMake 3.14 或更高版本中提供对这些平台的官方支持。
The above prevents code signing in code checks and try_compile() invocations,
but still leaves the code signing identity untouched in the main build (when
those details are provided).
Setting CMAKE_TRY_COMPILE_TARGET_TYPE prevents the checks from trying
to build bundles, which would require additional information such as a bundle
ID.
Other combinations of options can also be used, but the above is the simplest.
For tvOS and watchOS, variations on the above may work with CMake versions
prior to 3.14, but official support for those platforms is only provided
with CMake 3.14 or later.
以上内容涵盖了大多数 Apple 项目需要定义的主要与构建相关的设置。对于简单的未签名 macOS 应用程序,它们本身可能就足够了,但大多数项目需要进一步配置才能对构建产品进行签名,然后才能使用。
The above covers the main build-related settings that need to be defined for most Apple projects. For simple unsigned macOS apps, they may be enough on their own, but most projects will need further configuration to sign the build products before they can be useful.
与代码签名相关的 Xcode 功能随着每个连续的主要 Xcode 版本的发展而不断发展。代码签名和配置的自动管理使得使用 CMake 构建签名应用程序变得更加容易,但仍然需要了解签名过程以设置适当的属性和变量。应该指出的是,在 Xcode 8 中,签名和配置的工作方式发生了显着变化,留下了许多演示 Xcode 7 及更早版本的方法的示例,不再反映最佳实践。
Xcode functionality related to code signing has evolved with each successive major Xcode release. The move toward automatic management of code signing and provisioning has made it easier to get signed applications built with CMake, but it still requires an understanding of the signing process to set the appropriate properties and variables. It should be noted that in Xcode 8, the way that signing and provisioning works changed significantly, leaving many examples which demonstrate methods for Xcode 7 and earlier no longer reflecting best practice.
为了使签名和配置正常工作,应用程序必须具有有效的捆绑 ID 和至少两个其他关键信息:开发团队 ID 和代码签名身份。这些需要指定为 Xcode 属性,这些属性遵循通过目标属性或通过 CMake 变量在各个目标上设置的通常模式来指定全局默认值。由于这两个数量通常需要在整个构建过程中保持相同,因此通常建议将它们设置为项目顶部的变量,而不是每个目标。
For signing and provisioning to work, the app must have a valid bundle ID and at least two other key pieces of information: the development team ID and the code signing identity. These need to be specified as Xcode attributes, which follow the usual pattern of being set on individual targets through target properties or through CMake variables to specify global defaults. Since both quantities would typically need to be the same throughout the build, it is generally advisable to set them as variables at the top of the project rather than per target.
目标XCODE_ATTRIBUTE_DEVELOPMENT_TEAM属性或相应的CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM变量应设置为开发团队 ID,它是一个通常约为 10 个字符的短字符串。最方便的方法通常是
CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM在最顶层文件的早期设置变量
CMakeLists.txt,通常就在第一个project()命令之后。根据项目的不同,开发人员可能需要也可能不需要更改此值。例如,如果项目是始终由员工构建的公司软件,那么团队 ID 可能永远不会改变,而向公众开放的开源项目几乎肯定是由开发人员使用自己的开发团队 ID 构建的。对于团队 ID 永远不应该更改的情况,定义
CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM
为普通变量就足够了,但如果预计开发人员可能需要更改它,则应将其定义为缓存变量,以便可以给定默认值,但开发人员可以覆盖它而不编辑CMakeLists.txt文件。
The XCODE_ATTRIBUTE_DEVELOPMENT_TEAM target property or alternatively the
corresponding CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM variable should be
set to the development team ID, which is a short string typically of around 10
characters. The most convenient approach is usually to set the
CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM variable very early in the very top
CMakeLists.txt file, usually just after the first project() command.
Depending on the project, the developer might or might not need the ability to
change this value. For example, if the project is company software that will
always be built by an employee, then the team ID will likely never change,
whereas an open source project available to the general public will almost
certainly be built by developers with their own development team ID. For cases
where the team ID should never change, defining
CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM
as an ordinary variable is sufficient, but where it is expected that the
developer may need to change it, it should be defined as a cache variable so
that a default value can be given but developers can override it without
editing the CMakeLists.txt file.
类似地,XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY目标属性或相应的CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY变量指定签名身份。Mac Developer在过去,这几乎总是macOS 应用程序或iPhone DeveloperiOS、tvOS 或 watchOS 应用程序的字符串。Apple 自此更改了其证书处理方式,从 Xcode 11 开始,该字符串Apple Development应该适用于所有平台。这些值将指示 Xcode 为指定的开发团队选择最合适的签名身份。在特殊情况下,签名身份可能需要设置为一个字符串,用于标识开发人员钥匙串中的特定代码签名身份,但开发人员有责任确保该身份属于指定的开发团队。
Similarly, the XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY target property or the
corresponding CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY variable specifies
the signing identity.
In the past, this should almost always have been the string Mac Developer for
macOS applications or iPhone Developer for iOS, tvOS or watchOS applications.
Apple have since changed their certificate handling and starting with Xcode 11,
the string Apple Development should work for all platforms.
These values will direct Xcode to select the most appropriate signing identity
for the specified development team.
In unusual circumstances, the signing identity may need to be set to a string
which identifies a particular code signing identity in the developer’s
keychain, but the onus is then on the developer to ensure that this identity
belongs to the specified development team.
以下示例显示了如何CMakeLists.txt为 macOS 应用程序构建允许开发人员更改团队 ID 和签名身份的 macOS 应用程序:
The following example shows how a CMakeLists.txt might be structured for a
macOS application which allows the developer to change the team ID and the
signing identity:
cmake_minimum_required(VERSION 3.14)
project(macOSexample)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM
"ABC12345DE" CACHE STRING ""
)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY
"Apple Development" CACHE STRING ""
)cmake_minimum_required(VERSION 3.14)
project(macOSexample)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM
"ABC12345DE" CACHE STRING ""
)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY
"Apple Development" CACHE STRING ""
)
对于团队 ID 预计不会更改的 iOS 应用程序,但开发人员可能仍希望控制签名身份(例如,测试其钥匙串中的不同身份),仅身份需要是缓存变量:
For an iOS application where the team ID is not expected to be changed, but where the developer might still want control over the signing identity (e.g. to test a different identity in their keychain), only the identity would need to be a cache variable:
cmake_minimum_required(VERSION 3.14)
project(iOSexample)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "ABC12345DE")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY
"Apple Development" CACHE STRING ""
)cmake_minimum_required(VERSION 3.14)
project(iOSexample)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "ABC12345DE")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY
"Apple Development" CACHE STRING ""
)
人们可能会想将代码签名详细信息移动到工具链文件中以避免CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY完全设置,但正如第 23.5.4 节“编译器测试解决方法”中强调的那样,这可能会产生负面后果。CMake 作为第一个命令的一部分执行的尝试编译测试project()
将需要一个有效的配置文件,而这又需要一个有效的包 ID。通常不希望在团队帐户中创建此类捆绑包 ID 和配置文件。try-compile 测试不需要执行代码签名,因此不应使用工具链文件来全局启用签名。
One might be tempted to move the code signing details into a toolchain file to
avoid having to set CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY altogether,
but as highlighted in Section 23.5.4, “Compiler Test Workarounds”, this can have negative
consequences.
The try-compile test that CMake performs as part of the first project()
command would then require a valid provisioning profile, which in turn would
require a valid bundle ID.
It is not generally going to be desirable to have such bundle IDs and
provisioning profiles being created in the team account.
The try-compile tests do not need to perform code signing, so a toolchain file
should not be used to enable signing globally.
如果应用程序链接到也由项目构建的共享框架,则不要为这些框架启用代码签名。将此类框架添加到应用程序包的推荐方法是在启用“复制代码签名”选项的情况下嵌入它们 。这将在第 23.10 节“嵌入框架”中讨论。
If the app links to shared frameworks that are also built by the project, do not enable code signing for those frameworks. The recommended way to add such frameworks to an app bundle is to embed them with the Code sign on copy option enabled. This is discussed in Section 23.10, “Embedding Frameworks”.
当按照上一节所述进行配置时,Xcode 将自动选择适当的配置文件。如果不存在合适的配置文件,Xcode IDE 可以自动创建一个。命令行工具xcodebuild还提供
-allowProvisioningUpdates适用于 Xcode 9 或更高版本的选项。与早期 Xcode 版本的手动过程相比,自动配置可以更方便,在早期版本中,必须通过在线开发人员门户手动创建配置文件。然而,为了实现自动配置,开发人员仍然需要在 Apple 开发人员门户上拥有一个帐户。对于持续集成构建或单个开发人员可能没有此类帐户的团队环境来说,这可能是一个问题。在这些情况下,手动配置最终可能仍然是首选。
When configured as described in the preceding section, Xcode will automatically
select an appropriate provisioning profile.
If an appropriate profile doesn’t exist, the Xcode IDE can automatically create
one.
The xcodebuild command line tool also provides the
-allowProvisioningUpdates option for Xcode 9 or later.
Automatic provisioning can be more convenient compared to the manual process of
earlier Xcode versions where provisioning profiles had to be created manually
through the online developer portal.
For automatic provisioning to work, however, the developer still needs an
account on Apple’s developer portal.
This can be a problem for continuous integration builds or for team
environments where individual developers might not have such an account.
In those situations, manual provisioning may ultimately still be preferred.
目标XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER属性允许直接指定配置文件。应为其提供配置配置文件的名称而不是其 UUID,因为配置文件需要不时更新,例如在添加、更新或删除设备或开发人员证书时。配置文件的名称将保持不变,但其 UUID 将随着每次更新而改变。要使用的名称是开发人员门户中的配置文件显示的名称。
The XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER target property allows
the provisioning profile to be specified directly.
It should be given the provisioning profile’s name rather than its UUID because
the profile will need to be updated from time to time, such as when a device or
developer certificate is added, updated or removed.
The profile’s name will remain constant, but its UUID will change with each
update.
The name to use is the one shown for the profile in the developer portal.
Apple 应用程序也有一组相关的权利。这些控制操作系统将允许应用程序使用哪些功能,例如 Siri、推送通知等。在 Xcode IDE 内的项目设置中,用户可以转到应用程序目标的“功能”选项卡并打开所需的功能。然后,关联的权利会在 Xcode 自动生成的 plist 文件中启用,目标会链接到任何所需的框架,并且该功能会添加到团队帐户中的应用程序 ID 中。对于 CMake 生成的项目,此“功能”选项卡被有效绕过。相反,如果默认权利不够,CMake 项目预计会提供自己的权利 plist 文件。该项目还必须处理任何所需框架本身的链接,并且不会对应用程序 ID 进行任何更改。实际上,对于许多应用程序来说,这些限制相当温和,只有框架链接会带来一些问题。
Apple applications also have an associated set of entitlements. These control which features the operating system will allow the app to use, such as Siri, push notifications and so on. In the project settings within the Xcode IDE, users are able to go to the Capabilities tab of an app target and turn on the capabilities required. The associated entitlements are then enabled in the plist file that Xcode automatically generates, the target is linked to any required frameworks and the capability is added to the app ID in the team account. With a CMake-generated project, this Capabilities tab is effectively bypassed. Instead, the CMake project is expected to provide its own entitlements plist file if the default entitlements are not sufficient. The project must also handle linking of any required frameworks itself and no changes are made to the app ID. In practice, for many applications these are fairly mild restrictions, with only framework linking presenting some wrinkles.
通过将目标属性设置
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS为适当的权利文件的名称来指定权利,如下所示:
Specifying entitlements is done by setting the
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS target property to the name of an
appropriate entitlements file like so:
set_target_properties(MyApp PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS
${CMAKE_CURRENT_LIST_DIR}/MyApp.entitlements
)set_target_properties(MyApp PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS
${CMAKE_CURRENT_LIST_DIR}/MyApp.entitlements
)
举个例子,将 Siri 添加到默认权利的权利文件可以非常简单:
As an example, an entitlements file which adds Siri to the default entitlements can be quite simple:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.siri</key>
<true/>
</dict>
</plist><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.siri</key>
<true/>
</dict>
</plist>
为了通过 App Store、企业分发门户或临时分发来分发应用程序,首先需要创建存档。虽然 CMake 不会创建用于创建此类存档的构建目标,但该
xcodebuild工具可以与 CMake 生成的项目一起使用来完成任务。构建archive操作只需要几个选项就能够构建发布所需的目标并创建存档。有几种方法可以指定要归档的内容,但一个相当简单的方法是命名项目、方案和输出的名称,可以像这样完成:
In order to distribute an app via the App Store, an Enterprise distribution
portal or ad hoc distribution, an archive first needs to be created. While
CMake doesn’t create a build target for creating such an archive, the
xcodebuild tool can be used with a project generated by CMake to accomplish
the task. The archive build action requires only a few options to be able to
build the necessary targets for release and create an archive. There are a few
ways to specify what to archive, but a fairly simple approach is to name the
project, scheme and the name of the output, which can be done like so:
xcodebuild 存档 \
-项目 MyProject.xcodeproj \
-方案我的应用程序\
-archivePath MyApp.xcarchivexcodebuild archive \
-project MyProject.xcodeproj \
-scheme MyApp \
-archivePath MyApp.xcarchive.xcodeprojCMake在使用 Xcode 生成器时创建该文件。在 CMake 3.9 之前,用户必须在 Xcode IDE 中加载项目才能创建构建方案。这给无头持续集成构建带来了无法访问 IDE 的问题,因此为了解决这种情况,CMake 3.9 引入了该CMAKE_XCODE_GENERATE_SCHEME变量。当此变量设置为 true 时,CMake 还将生成用于构建的架构文件,然后允许为选项指定应用程序目标的名称,-scheme并且存档任务具有其所需的所有信息。上述命令将为MyApp所有支持的架构构建发布配置的目标,对其进行签名(仍使用开发人员签名身份),然后
MyApp.archive在当前目录中创建一个名为的存档。
CMake creates the .xcodeproj file when using the Xcode generator. Prior to
CMake 3.9, the user then had to load the project in the Xcode IDE to create the
build schemes. This presented a problem for headless continuous integration
builds where the IDE cannot be accessed, so to address this situation, CMake
3.9 introduced the CMAKE_XCODE_GENERATE_SCHEME variable.
When this variable is set to true, CMake will also
generate schema files for the build, which then allows the name of the app
target to be specified for the -scheme option and the archive task has all
the information it needs. The above command will build the MyApp target for
the Release configuration for all supported architectures, sign it (still with
the developer signing identity), and then create an archive named
MyApp.archive in the current directory.
如果未正确设置某些安装属性,存档可能会失败。Apple 开发人员文档包含一些故障排除指南,这些指南可能有助于克服更常见的情况,其中一些更相关的指南是确保为目标类型正确设置目标INSTALL_PATH和属性。SKIP_INSTALL在旨在生成用于分发的签名应用程序的 CMake 项目中,XCODE_ATTRIBUTE_SKIP_INSTALL
必须将目标的属性设置YES为库和嵌入式框架以及NO
应用程序。如果设置为NO,则XCODE_ATTRIBUTE_INSTALL_PATH
还必须提供 ,并且通常应给出该值
$(LOCAL_APPS_DIR)。如果不遵循此建议,通常会导致存档步骤生成通用存档而不是应用程序存档。
Archiving may fail if certain install attributes are not set appropriately. The
Apple developer documentation contains a few troubleshooting guidelines which
may help overcome the more common situations, some of the more relevant ones
being to ensure the target’s INSTALL_PATH and SKIP_INSTALL attributes are
set correctly for the target type. In a CMake project aimed at producing a
signed application for distribution, a target’s XCODE_ATTRIBUTE_SKIP_INSTALL
property must be set to YES for libraries and embedded frameworks and to NO
for applications. Where it is set to NO, the XCODE_ATTRIBUTE_INSTALL_PATH
must also be provided and it should generally be given the value
$(LOCAL_APPS_DIR). Failure to follow this advice will typically result in the
archiving step producing a generic archive rather than an application archive.
# Apps must have install step enabled
set_target_properties(macOSApp PROPERTIES
XCODE_ATTRIBUTE_SKIP_INSTALL NO
XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)"
)# Apps must have install step enabled
set_target_properties(macOSApp PROPERTIES
XCODE_ATTRIBUTE_SKIP_INSTALL NO
XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)"
)
创建应用程序存档后,需要将其导出以进行分发。这是通过xcodebuild再次运行该工具来实现的,这次提供刚刚创建的存档、一个选项 plist 文件以及将输出写入的位置。该命令的基本形式是:
After the application archive has been created, it needs to be exported
for distribution.
This is achieved by running the xcodebuild tool again, this time providing
the archive just created, an options plist file and the location to write the
output to.
The basic form of the command is:
xcodebuild -exportArchive \
-archivePath MyApp.xcarchive \
-exportOptionsPlist 导出选项.plist \
-exportPath产品xcodebuild -exportArchive \
-archivePath MyApp.xcarchive \
-exportOptionsPlist exportOptions.plist \
-exportPath Products该-archivePath选项指向先前调用创建的存档文件xcodebuild,并且该-exportPath选项指定在其中创建最终输出文件的目录。有关导出步骤的其他所有内容均由提供给该选项的 plist 文件定义-exportOptionsPlist。完整支持的键集可以在该工具的帮助文档 ( ) 中找到xcodebuild -help,但最小的 plist 文件可能如下所示:
The -archivePath option points to the archive file created by the earlier
invocation of xcodebuild and the -exportPath option specifies the directory
in which to create the final output file. Everything else about the export step
is defined by the plist file given to the -exportOptionsPlist option. The
full set of supported keys can be found in the tool’s help documentation
(xcodebuild -help), but a minimal plist file might look like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
</dict>
</plist><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
</dict>
</plist>
该方法指定预期的分销渠道,预计为以下之一:
The method specifies the intended distribution channel and is expected to be one of the following:
app-store
app-store
ad-hoc
ad-hoc
enterprise
enterprise
development
development
developer-id
developer-id
package
package
默认值为development,但更可能令人感兴趣的方法是app-store,enterprise或ad-hoc。导出存档时,该工具将为所选方法选择适当的分发签名身份并重新签名应用程序。开发人员应该已经创建或下载了适当的分发签名身份和配置文件(最容易在 Xcode IDE 中完成,但对于持续集成服务器等可以手动完成)。
The default is development, but the methods more likely to be of
interest are app-store, enterprise or ad-hoc.
When exporting an archive, the tool will select an appropriate distribution
signing identity for the chosen method and re-sign the app.
The developer is expected to have already created or downloaded the appropriate
distribution signing identity and provisioning profile (most easily done within
the Xcode IDE, but can be done manually for continuous integration servers,
etc.).
在针对 Apple 平台进行构建时,通常需要针对多种架构进行构建。对于给定的设备或桌面平台,可能存在多代 CPU 类型,通常需要为每种架构提供二进制文件。例如,iOS 的体系结构集通常包括armv7、和 的某种组合,具体取决于所使用的 SDK 和目标部署版本。另一个例子是 macOS,可能需要支持Intel ( ) 和 Apple Silicon ( ) 架构。设备平台通常还配备一组用于开发和测试目的的模拟器。这些模拟器在主机的体系结构上运行。armv7sarm64arm64ex86_64arm64
When building for Apple platforms, it will often be desirable to build for
multiple architectures.
For a given device or desktop platform, there may be multiple generations of
CPU type and it would usually be desirable to provide binaries for each
architecture.
For example, the set of architectures for iOS typically includes some
combination of armv7, armv7s, arm64 and arm64e, depending on the SDK
used and the deployment version being targeted.
Another example is for macOS, where it is likely that both Intel (x86_64)
and Apple Silicon (arm64) architectures will need to be supported.
The device platforms also typically each come with a set of simulators for
development and testing purposes.
These simulators run on the host machine’s architecture.
有两种主要场景需要构建多种架构:
There are two main scenarios where it is desirable to build for multiple architectures:
arm64和arm64e切片。
arm64 and arm64e slices in its binaries.
arm64、arm64e和x86_64
架构。对于框架,这更适合传递给其他开发人员或客户以在他们自己的项目构建中使用。
arm64, arm64e and x86_64
architectures.
In the case of frameworks, this would be more appropriate for passing along
to other developers or customers for use in their own project’s build.
这些场景给 CMake 构建带来了不同的问题,并且需要不同的解决方案。
These scenarios present different problems for CMake builds and require different solutions.
对于没有任何嵌入式框架的应用程序包,生成通用二进制文件不需要对项目CMakeLists.txt
文件进行任何其他更改或指定任何新的缓存变量。只需在构建命令行上指示要构建的架构并覆盖设置即可实现ONLY_ACTIVE_ARCH。包含组合二进制文件的最终应用程序包将使用与执行单个架构构建相同的设置进行签名。在以下两个示例中,这两个命令是等效的:
For an app bundle without any embedded frameworks, producing a universal
binary doesn’t require any additional changes to the project’s CMakeLists.txt
files or specifying any new cache variables.
It can be achieved just by indicating which architectures to build for on the
build command line and overriding the ONLY_ACTIVE_ARCH setting.
The final app bundle with the combined binaries will be signed using the same
settings as if doing a single architecture build.
In the following two examples, both commands are equivalent:
# 通过 CMake 构建
cmake --build 。--config 发布 --\
ONLY_ACTIVE_ARCH=否 \
-拱arm64 \
-拱arm64e# Building via CMake
cmake --build . --config Release -- \
ONLY_ACTIVE_ARCH=NO \
-arch arm64 \
-arch arm64e# 直接调用xcodebuild
xcodebuild -配置发布\
ONLY_ACTIVE_ARCH=否 \
-拱arm64 \
-拱arm64e# Invoking xcodebuild directly
xcodebuild -configuration Release \
ONLY_ACTIVE_ARCH=NO \
-arch arm64 \
-arch arm64e当通过 构建时cmake, 后面的所有选项--都会直接传递到底层构建工具(xcodebuild当使用 Xcode 生成器时)。如果从 Xcode IDE 中构建,则ONLY_ACTIVE_ARCH可以将应用程序包目标的构建设置设置为NO,然后以通常的方式执行构建。
When building via cmake, all options after the -- are passed directly to
the underlying build tool (xcodebuild when using the Xcode generator).
If building from within the Xcode IDE, the ONLY_ACTIVE_ARCH build setting for
the app bundle’s target can be set to NO and the build is then performed in
the usual way.
生成存档时cmake --build无法使用,xcodebuild
必须直接调用该工具。生成存档时也需要提供相同的体系结构信息。以下扩展了第 23.7 节“创建和导出档案”中的示例,以将档案构建为通用二进制文件:
When producing an archive, cmake --build cannot be used and the xcodebuild
tool must be invoked directly.
The same architecture information needs to be given when producing an archive
as well.
The following extends the example from Section 23.7, “Creating And Exporting Archives” to
build an archive as a universal binary:
xcodebuild 存档 \
-项目 MyProject.xcodeproj \
-方案我的应用程序\
-archivePath MyApp.xcarchive \
ONLY_ACTIVE_ARCH=否 \
-拱arm64 \
-拱arm64excodebuild archive \
-project MyProject.xcodeproj \
-scheme MyApp \
-archivePath MyApp.xcarchive \
ONLY_ACTIVE_ARCH=NO \
-arch arm64 \
-arch arm64e然后可以按照与单个架构应用程序相同的方式导出和上传存档。
The archive can then be exported and uploaded in the same way as for a single architecture app.
在线教程和示例建议在项目CMakeLists.txt文件中设置体系结构或在命令行上设置缓存变量是很常见的cmake。他们设置CMAKE_OSX_ARCHITECTURES变量,有时也设置
CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH为 false。这些具有更改构建的默认行为以始终生成具有指定架构的通用二进制文件的效果。例如:
It is very common to see online tutorials and examples recommend setting the
architectures in the project’s CMakeLists.txt files or as cache variables on
the cmake command line.
They set the CMAKE_OSX_ARCHITECTURES variable and sometimes also set
CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH to false.
These have the effect of changing the default behavior of the build to always
produce universal binaries with the specified architectures.
For example:
cmake -G Xcode \
-D CMAKE_TOOLCHAIN_FILE=../toolchain.cmake \
-D "CMAKE_OSX_ARCHITECTURES:STRING=arm64;arm64e" \
-D CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH:BOOL=NO \
路径/到/源cmake -G Xcode \
-D CMAKE_TOOLCHAIN_FILE=../toolchain.cmake \
-D "CMAKE_OSX_ARCHITECTURES:STRING=arm64;arm64e" \
-D CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH:BOOL=NO \
path/to/source这是否可取将取决于具体情况。对于日常开发,开发人员很可能只需要构建一种架构。在这种情况下,如果项目对架构进行硬编码并强制构建通用二进制文件,那将会很麻烦,因为它每次都会构建超出开发人员需要的内容。相反,项目应该忽略这些细节,并让开发人员决定他们是否想要默认的行为。上面的示例显示了开发人员或自动化脚本如何单独使用 CMake 缓存变量来设置默认值。人们可以认为项目可以指定一组架构,但仍然让开发人员选择是否设置
CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH。如果项目这样做,它不应该强制 的值
CMAKE_OSX_ARCHITECTURES,而是将其设置为缓存变量,如果开发人员或驱动构建的脚本需要,该变量仍然可以被覆盖。
Whether this is desirable will depend on the situation.
For day-to-day development, it is likely that the developer will only need to
build one architecture.
In that case, it would be a nuisance if the project hard-coded the
architectures and forced building universal binaries, since it would build more
than the developer needs each time.
Instead, the project should omit those details and let the developer decide if
they want that behavior by default or not.
The above example shows how the developer or an automated script can set the
defaults using CMake cache variables alone.
One could make the argument that the project could specify the set of
architectures, but still leave the developer to choose whether to set
CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH or not.
If the project does this, it should not force the value of
CMAKE_OSX_ARCHITECTURES, but rather set it as a cache variable which can
still be overridden if the developer or a script driving the build needs to.
开发人员应该意识到设置的副作用
CMAKE_OSX_ARCHITECTURES。除了一个例外(请参阅下一节),编译器检查将测试该变量中列出的体系结构,而不仅仅是默认体系结构。这不仅会增加配置时间,而且如果这些编译器检查使用的部署版本与所请求的任何体系结构不兼容,还可能会中断配置步骤。如果在第一个命令之前未通过设置缓存变量进行全局指定
project(),则部署版本将使用 SDK 支持的最新版本。对于 iOS,架构的部署版本要求为 10 或更低
armv7,这意味着如果不显式设置部署版本,使用任何合理的当前 Xcode 版本默认情况下都会崩溃。可能需要在命令行-DCMAKE_OSX_DEPLOYMENT_TARGET=10中添加类似的内容来克服这种情况。cmake
Developers should be aware of a side effect of setting
CMAKE_OSX_ARCHITECTURES.
With one exception (see next section), the compiler checks will test the
architectures listed in that variable instead of just the default
architecture.
This not only increases the configure time, it can break the configure step if
the deployment version used by those compiler checks is incompatible with any
of the architectures requested.
If not specified globally through a cache variable set before the first
project() command, the latest version supported by the SDK will be used for
the deployment version.
In the case of iOS, the deployment version is required to be 10 or less for the
armv7 architecture, which means using any reasonably current Xcode version
will break by default without explicitly setting the deployment version.
Adding something like -DCMAKE_OSX_DEPLOYMENT_TARGET=10 to the cmake command
line may be needed to overcome this situation.
使用最近的 CMake 和 Xcode,为 Intel 和 Apple Silicon 生成带切片的 macOS 通用二进制文件与仅设备的情况非常相似。事实上,可以通过设置CMAKE_OSX_ARCHITECTURES
所需的架构以相同的方式处理它。不过,使用特殊的 Xcode 变量可能更方便,而不是列出各个体系结构$(ARCHS_STANDARD)。这将扩展到所选 SDK(默认为 macOS)的特定 Xcode 版本支持的全套架构。当至少使用 CMake 3.19.2 和 Xcode 12.2 时,以下内容足以生成包含 Intel 和 Apple Silicon 切片的通用二进制文件:
With a recent CMake and Xcode, producing macOS universal binaries with slices
for Intel and Apple Silicon is very similar to the device-only case.
In fact, it can be handled the same way, by setting CMAKE_OSX_ARCHITECTURES
to the desired architectures.
Instead of listing the individual architectures though, it may be more
convenient to use the special Xcode variable $(ARCHS_STANDARD).
This will expand to the full set of architectures supported by that particular
version of Xcode for the selected SDK (macOS by default).
When using at least CMake 3.19.2 and Xcode 12.2, the following is sufficient to
produce a universal binary containing Intel and Apple Silicon slices:
cmake -G Xcode \
-D "CMAKE_OSX_ARCHITECTURES:STRING=$(ARCHS_STANDARD)" \
路径/到/源cmake -G Xcode \
-D "CMAKE_OSX_ARCHITECTURES:STRING=$(ARCHS_STANDARD)" \
path/to/sourceCMake 3.19 还确保初始编译器检查仅在使用 Xcode 生成器定位 macOS 平台时测试主机架构。这与针对其中一个设备平台形成鲜明对比,在该平台上,CMAKE_OSX_ARCHITECTURES将测试由 指定的所有架构。
CMake 3.19 also ensures that the initial compiler checks only test the host
architecture when targeting the macOS platform with the Xcode generator.
This is in contrast to targeting one of the device platforms, where all
architectures specified by CMAKE_OSX_ARCHITECTURES would be tested.
如果CMAKE_OSX_ARCHITECTURES未设置,CMake 默认情况下将仅针对一种架构进行构建。选择可能会受到主机架构、CMake 是否在 Rosetta 下运行以及 CMake 本身是否已构建为通用二进制文件的影响。对于 CMake 3.19.2 或更高版本,官方 CMake 包包含通用二进制文件。然后选择执行哪个切片的默认架构。在英特尔主机上,这将是英特尔切片。在 Apple Silicon 主机上,这通常是 Apple Silicon 切片,但如果 CMake 被迫在 Rosetta 下运行,它将选择 Intel 架构。如果需要,用户可以通过设置名为CMAKE_APPLE_SILICON_PROCESSOR或x86_64的CMake 缓存变量来覆盖默认选择arm64。或者,CMAKE_APPLE_SILICON_PROCESSOR可以以相同的方式使用环境变量。如果在 Apple Silicon 主机上构建,请避免使用 3.19.2 之前的 CMake 版本。
If CMAKE_OSX_ARCHITECTURES is not set, CMake will only build for one
architecture by default.
The choice may be influenced by the host machine’s architecture, by whether
CMake is running under Rosetta and by whether CMake itself has been built as
a universal binary.
With CMake 3.19.2 or later, the official CMake packages contain universal
binaries.
The default architecture is then chosen by which slice is being executed.
On an Intel host, this will be the Intel slice.
On an Apple Silicon host, this will normally be the Apple Silicon slice,
but if CMake is being forced to run under Rosetta, it will select the Intel
architecture instead.
If required, the user can override the default choice by setting a CMake cache
variable named CMAKE_APPLE_SILICON_PROCESSOR to either x86_64 or arm64.
Alternatively, a CMAKE_APPLE_SILICON_PROCESSOR environment variable can be
used in the same way instead.
Avoid CMake versions before 3.19.2 if building on an Apple Silicon host.
当构建只需要单个 SDK 时,前面部分的方法适用。为设备及其模拟器进行构建时,涉及两个 SDK,并且生成通用二进制文件需要更多步骤。人们可以进行两次单独的构建,然后使用lipo命令手动将生成的二进制文件拼接在一起,或者可以将项目配置为在安装过程中自动进行构建和拼接。该项目需要提供所需的install()命令才能使其正常工作,这在第 26 章“安装”中进行了介绍。特别相关的材料可以在第 26.2.3 节“Apple 特定目标”中找到。
The methods of the previous sections are suitable when only a single SDK is
needed for the build.
When building for both a device and its simulators, there are two SDKs
involved and producing universal binaries requires more steps.
One can either do two separate builds and then use the lipo command to
manually stitch the resultant binaries together, or the project can be
configured to do the building and stitching automatically as part of an
install.
The project needs to provide the required install() commands for this to
work, which is covered in Chapter 26, Installing.
Particularly relevant material can be found in Section 26.2.3, “Apple-specific Targets”.
要指示 CMake 构建并安装设备和模拟器版本,
CMAKE_IOS_INSTALL_COMBINED需要将该变量设置为 true。这也可以使用 target 属性在每个目标的基础上进行设置IOS_INSTALL_COMBINED
,该属性在定义目标时由变量的值初始化
CMAKE_IOS_INSTALL_COMBINED,但设置变量通常会更方便。使用此功能,可以使用如下命令创建组合设备和模拟器切片的通用二进制文件:
To direct CMake to build and install both device and simulator builds, the
CMAKE_IOS_INSTALL_COMBINED variable needs to be set to true.
This can also be set on a per-target basis using the IOS_INSTALL_COMBINED
target property, which is initialized by the value of the
CMAKE_IOS_INSTALL_COMBINED variable when the target is defined, but setting
the variable would typically be more convenient.
Using this feature, universal binaries combining device and simulator slices
could be created with commands like the following:
cmake -G Xcode \
-D CMAKE_TOOLCHAIN_FILE=someToolchain.cmake \
-D "CMAKE_OSX_ARCHITECTURES=arm64;arm64e;x86_64" \
-D CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \
-D CMAKE_IOS_INSTALL_COMBINED=YES \
-D CMAKE_INSTALL_PREFIX=路径/到/暂存/区域 \
路径/到/源
cmake --build 。--config 发布 --目标安装cmake -G Xcode \
-D CMAKE_TOOLCHAIN_FILE=someToolchain.cmake \
-D "CMAKE_OSX_ARCHITECTURES=arm64;arm64e;x86_64" \
-D CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \
-D CMAKE_IOS_INSTALL_COMBINED=YES \
-D CMAKE_INSTALL_PREFIX=path/to/staging/area \
path/to/source
cmake --build . --config Release --target install如果使用 Xcode 12 或更高版本在 Apple Silicon 主机上进行构建,则可能还需要阻止arm64为模拟器构建添加架构。这可以避免尝试为设备和模拟器lipo添加切片的情况。arm64实现此目的的一种方法是添加排除项,这是 Xcode 12 的一项新功能。以下示例显示了一种使用 Xcode 项目设置的变体形式来定位 iOS 的方法:
If building on an Apple Silicon host with Xcode 12 or later, it may also be
necessary to prevent the arm64 architecture from being added for simulator
builds.
This can avoid the situation where lipo tries to add arm64 slices for
both the device and simulator.
One way to achieve this is by adding an exclusion, which is a new feature
for Xcode 12.
The following example shows one method for targeting iOS using the variant
form of Xcode project settings:
set(CMAKE_XCODE_ATTRIBUTE_EXCLUDED_ARCHS[sdk=iphonesimulator*] arm64)set(CMAKE_XCODE_ATTRIBUTE_EXCLUDED_ARCHS[sdk=iphonesimulator*] arm64)
应用程序包通常链接到框架以重用功能。这些框架可以由 SDK、外部提供商提供,也可以由项目构建。外部各方提供的框架是最容易合并到项目中的。它们的链接方式target_link_libraries()就像任何其他库一样。首先需要找到它们,这通常通过调用
find_package()或 来完成find_library()(请参阅第 24 章,查找事物)。
App bundles often link against frameworks to re-use functionality.
These frameworks could be provided by the SDK, by external providers or they
could be built by the project.
Frameworks provided by external parties are the most straightforward to
incorporate into the project.
They are linked using target_link_libraries() just like any other library.
They will first need to be located, which is usually done by a call to
find_package() or find_library() (see Chapter 24, Finding Things).
Xcode SDK 提供的框架也相当容易处理。Xcode 会自动添加 SDK 框架的相关搜索路径,因此可以使用表单链接它们-framework FrameworkName,而无需显式查找。为了防止 CMake 错误地合并这些选项,必须使用引号将选项与要链接的框架的名称放在一起。
Frameworks provided by the Xcode SDK are also fairly straightforward to
handle.
Xcode automatically adds the relevant search path for the SDK’s frameworks,
so they can be linked using the form -framework FrameworkName and don’t
need to be found explicitly.
To prevent CMake from erroneously merging these options, quoting must be used
to keep the option together with the name of the framework to link.
target_link_libraries(MyApp PRIVATE
"-framework ARKit"
"-framework HomeKit"
)target_link_libraries(MyApp PRIVATE
"-framework ARKit"
"-framework HomeKit"
)
当链接项目构建的框架时,事情会变得更加复杂。创建存档时,Xcode 使用与普通构建完全不同的路径来构建项目。在归档过程中构建的框架不会位于 CMake 期望的位置。使用 CMake 3.19 或更高版本时,链接器命令行的构造方式不会直接使用框架的完整路径。target_link_libraries()这允许使用常用命令,并且尽管路径不同,链接也会成功。对于 CMake 3.18 及更早版本,错误的路径会破坏存档构建,因此需要在不直接引用 CMake 目标的情况下完成链接。链接-framework器选项可以再次用于此目的,后面紧跟框架的输出名称。此输出名称通常与目标名称相同,但可以使用OUTPUT_NAME目标属性进行更改。还需要添加某种依赖关系,以确保在链接可执行文件之前构建框架。为了简单起见,以下示例假设使用默认输出名称:
It gets more complicated when linking a framework built by the project.
When creating an archive, Xcode uses completely different paths to build the
project compared to an ordinary build.
The frameworks built during the archive process will not be at the locations
CMake expects them to be.
When using CMake 3.19 or later, the linker command lines are constructed in
such a way that the full path to the framework isn’t used directly.
This allows the usual target_link_libraries() command to be used and linking
will succeed despite the different paths.
For CMake 3.18 and earlier, the wrong path breaks archive builds, so linking
needs to be done without referring to the CMake target directly.
The -framework linker option can once again be used for this, following it
with the output name of the framework.
This output name is usually the same as the target name, but can be changed
with the OUTPUT_NAME target property.
Some kind of dependency also needs to be added to ensure the framework is
built before the executable is linked.
The following example assumes the default output name for simplicity:
# Workaround for CMake 3.18 and earlier
target_link_libraries(MyApp PRIVATE "-framework MyFwk")
add_dependencies(MyApp MyFwk)# Workaround for CMake 3.18 and earlier
target_link_libraries(MyApp PRIVATE "-framework MyFwk")
add_dependencies(MyApp MyFwk)
失去与 CMake 目标的直接链接的一个缺点是使用需求不会从框架目标传播到可执行目标。任何框架PUBLIC或INTERFACE属性都不会应用于可执行目标。如果这些是相关的,该项目将需要找到另一种方法来实现这一目标。一种方法是创建一个单独的接口库,框架和可执行目标都链接到该接口库。这是相当脆弱和不方便的,所以尽可能使用 CMake 3.19 或更高版本。
A drawback to losing the direct linking to the CMake target is that usage
requirements will not be propagated from the framework target to the
executable target.
None of the framework’s PUBLIC or INTERFACE properties will be applied to
the executable target.
If these are relevant, the project will need to find another way to achieve
this.
One method would be to create a separate interface library that both the
framework and the executable targets link to.
That is fairly fragile and inconvenient, so prefer to use CMake 3.19 or later
where possible.
上面概述的技术可以扩展到框架与另一个框架链接的情况:
The technique outlined above can be extended to cases where a framework links against another framework:
add_executable(MyApp ...)
add_library(MyFwk ...)
add_library(OtherFwk ...)
target_link_libraries(MyFwk PRIVATE "-framework OtherFwk")
target_link_libraries(MyApp PRIVATE "-framework MyFwk")
add_dependencies(MyFwk OtherFwk)
add_dependencies(MyApp MyFwk)add_executable(MyApp ...)
add_library(MyFwk ...)
add_library(OtherFwk ...)
target_link_libraries(MyFwk PRIVATE "-framework OtherFwk")
target_link_libraries(MyApp PRIVATE "-framework MyFwk")
add_dependencies(MyFwk OtherFwk)
add_dependencies(MyApp MyFwk)
OtherFwk同样的警告也适用于从使用此方法传播到使用此方法的使用要求的损失
MyFwk。
The same caveat applies regarding loss of usage requirements propagating from
OtherFwk to MyFwk using this approach.
CMake 3.19 引入了对使用 Xcode 的Link Binary With Libraries
构建阶段的支持。新的目标属性XCODE_LINK_BUILD_PHASE_MODE可用于控制 CMake 将库和框架链接到某些目标类型的方式。此属性的默认值会导致与早期 CMake 版本相同的行为,但它不再以会破坏存档操作的方式在链接器标志中嵌入路径xcodebuild。这就是为什么使用 CMake 3.19 或更高版本可以避免上述脆弱的解决方法。
CMake 3.19 introduced support for using Xcode’s Link Binary With Libraries
build phase.
A new target property XCODE_LINK_BUILD_PHASE_MODE can be used to
control the way CMake links libraries and frameworks into some target types.
The default value of this property results in the same behavior as earlier
CMake versions, except it no longer embeds paths in linker flags in a way
that would break an xcodebuild archive operation.
This is why the above fragile workaround can be avoided with CMake 3.19 or
later.
XCODE_LINK_BUILD_PHASE_MODE通过将目标的属性设置为,可以获得更熟悉的 Xcode 体验
BUILT_ONLY。由项目构建并链接到目标的任何其他 CMake 目标将通过“链接二进制与库”构建阶段进行链接,而不是使用原始链接器标志。该属性也可以设置为KNOWN_LOCATION,在这种情况下,CMake 知道的任何项目都将通过此构建阶段链接,而不仅仅是项目构建的 CMake 目标。仅通过构建阶段使用
KNOWN_LOCATION但不BUILT_ONLY包括指定为完整路径的导入目标和外部提供的库进行链接的项目示例。
A more familiar Xcode experience can be obtained by setting a target’s
XCODE_LINK_BUILD_PHASE_MODE property to BUILT_ONLY.
Any other CMake targets that are built by the project and linked into the
target will then be linked via the Link Binary With Libraries build phase
instead of using raw linker flags.
The property can alternatively be set to KNOWN_LOCATION, in which case any
item CMake knows the path to will be linked via this build phase, not just
CMake targets built by the project.
Examples of items that would only be linked via the build phase using
KNOWN_LOCATION but not BUILT_ONLY include imported targets and externally
provided libraries specified as full paths.
对于共享框架,除了链接之外还需要考虑其他步骤。该项目需要将共享框架嵌入到应用程序包中,以便它在运行时可用。CMake 3.20 通过 Xcode 生成器添加了对此的直接支持。项目可以指定要嵌入目标属性的框架列表
XCODE_EMBED_FRAMEWORKS。这些可以是 CMake 目标或框架的路径(它们也可以是裸库的路径,但这将是不寻常的,并且与使用框架的正常实践不一致)。默认情况下,Xcode 会将框架复制到捆绑包内的标准位置。如果需要,项目可以使用该
XCODE_EMBED_FRAMEWORKS_PATH属性覆盖此位置,但这通常不是必需的。
For shared frameworks, there are further steps to consider beyond linking.
The project will need to embed the shared framework into the app bundle so
that it is available at run time.
CMake 3.20 added direct support for this with the Xcode generator.
Projects can specify a list of frameworks to embed with the
XCODE_EMBED_FRAMEWORKS target property.
These can be CMake targets or paths to frameworks (they can also be paths to
bare libraries, but that would be unusual and not consistent with normal
practice of using frameworks).
Xcode will copy the frameworks to a standard location within the bundle by
default.
If required, the project can override this location with the
XCODE_EMBED_FRAMEWORKS_PATH property, but that should not normally be
necessary.
嵌入框架时,项目需要决定是否要包含框架标头。如果嵌入的目标是应用程序包,则标头可能是不必要的并且应该被排除。另一方面,如果嵌入的目标本身就是一个框架,那么包含标头可能是合理的。项目可以使用
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY嵌入目标的布尔属性来指定它想要的行为。它将适用于嵌入该目标的所有框架。
When embedding a framework, the project needs to decide whether or not it
wants the framework headers to be included.
If the target being embedded into is an application bundle, then the headers
would likely be unnecessary and should be excluded.
On the other hand, if the target being embedded into is itself a framework,
then it may be reasonable to include the headers.
The project can specify the behavior it wants with the boolean
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY property on the target
being embedded into.
It will apply to all frameworks being embedded into that target.
同样,项目可能希望表明是否对框架进行代码签名作为嵌入框架的一部分。XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY
这可以通过嵌入目标的布尔属性来完成。如果启用此功能,请确保框架在构建时未签名,否则 Xcode 会抱怨尝试签署已签名的框架。
Similarly, the project will likely want to indicate whether to code-sign the
frameworks as part of embedding them.
This can be done with the boolean XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY
property on the target being embedded into.
If enabling this feature, ensure that the frameworks are not signed when they
are built or else Xcode will complain about trying to sign an already-signed
framework.
以下示例演示了如何将上述内容应用于具有单个嵌入式框架的应用程序:
The following example demonstrates how to apply the above for an application with a single embedded framework:
set(CMAKE_BUILD_WITH_INSTALL_RPATH)
add_library(MyFwk SHARED ...)
add_executable(MyApp MACOSX_BUNDLE ...)
target_link_libraries(MyApp PRIVATE MyFwk)
set_target_properties(MyFwk PROPERTIES
FRAMEWORK TRUE
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED FALSE
...
)
set_target_properties(MyApp PROPERTIES
# CMake 3.20 or later required
XCODE_EMBED_FRAMEWORKS MyFwk
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY TRUE
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY TRUE
...
)set(CMAKE_BUILD_WITH_INSTALL_RPATH)
add_library(MyFwk SHARED ...)
add_executable(MyApp MACOSX_BUNDLE ...)
target_link_libraries(MyApp PRIVATE MyFwk)
set_target_properties(MyFwk PROPERTIES
FRAMEWORK TRUE
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED FALSE
...
)
set_target_properties(MyApp PROPERTIES
# CMake 3.20 or later required
XCODE_EMBED_FRAMEWORKS MyFwk
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY TRUE
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY TRUE
...
)
需要对上述示例进行进一步调整才能完成完整的图片。目标属性如XCODE_ATTRIBUTE_SKIP_INSTALL和
XCODE_ATTRIBUTE_INSTALL_PATH需要适当设置,如
第 23.7 节“创建和导出档案”中所述。二进制文件还需要能够在运行时找到彼此,这需要在二进制文件本身中嵌入更多详细信息。该主题在第 26.2.2 节“RPATH”和第 26.2.3 节“Apple 特定目标”中讨论。
Further tweaks to the above example are needed to complete the full picture.
Target properties like XCODE_ATTRIBUTE_SKIP_INSTALL and
XCODE_ATTRIBUTE_INSTALL_PATH need to be set appropriately, as discussed in
Section 23.7, “Creating And Exporting Archives”.
The binaries also need to be able to find each other at run time, which
requires embedding further details in the binaries themselves.
That topic is addressed in Section 26.2.2, “RPATH” and Section 26.2.3, “Apple-specific Targets”.
顺便说一句,CMake 3.21 扩展了属性集XCODE_EMBED_…以支持嵌入应用程序扩展。除了使用不同的属性默认值之外,开始的属性XCODE_EMBED_APP_EXTENSIONS与上面讨论的属性的工作方式类似。具体细节请参见CMake官方文档。XCODE_EMBED_FRAMEWORKS…_REMOVE_HEADERS_ON_COPY
On a side note, CMake 3.21 extended the set of XCODE_EMBED_… properties to
support embedding app extensions as well.
Properties starting with XCODE_EMBED_APP_EXTENSIONS work in a similar way to
the XCODE_EMBED_FRAMEWORKS properties discussed above, apart from using a
different default for the …_REMOVE_HEADERS_ON_COPY properties.
See the official CMake documentation for specific details.
使用 CMake 3.19 及更早版本嵌入框架就不那么简单了。需要更多的手动(且更脆弱)步骤才能达到类似的结果。使用 Xcode 生成器时,还可以通过将共享框架列为应用程序包可执行目标的源来提供解决方法。它们需要以一种不寻常的方式指定,以合理稳健地支持所有用例。
Embedding frameworks with CMake 3.19 and earlier is much less straightforward. More manual (and more fragile) steps are needed to achieve a similar result. When using the Xcode generator, a workaround is available by also listing the shared frameworks as sources for the app bundle’s executable target. They need to be specified in an unusual way to support all use cases reasonably robustly.
通常,嵌入源代码很简单,只需在对add_executable()or的调用中列出其路径即可target_sources()。对于外部提供的框架,情况就是如此,因为该框架的位置通常是固定的。然后,可以使用源MACOSX_PACKAGE_LOCATION文件属性在构建时将框架复制到应用程序包中,就像任何其他资源或文件一样。下面显示了如何在 macOS 平台上完成此操作,其中框架通常收集在Frameworks应用程序包内的目录下:
Ordinarily, embedding a source is a simple matter of listing its path in a
call to add_executable() or target_sources().
For externally provided frameworks, this is the case because the location of
that framework is typically fixed.
The MACOSX_PACKAGE_LOCATION source file property can then be used to copy the
framework into the app bundle at build time, just like any other resource or
file.
The following shows how that might be done for a macOS platform where
frameworks are usually collected under a Frameworks directory within the app
bundle:
add_executable(MyApp MACOSX_BUNDLE ... ${pathToFramework})
target_link_libraries(MyApp PRIVATE ${pathToFramework})
set_source_files_properties(${pathToFramework} PROPERTIES
MACOSX_PACKAGE_LOCATION Frameworks
)add_executable(MyApp MACOSX_BUNDLE ... ${pathToFramework})
target_link_libraries(MyApp PRIVATE ${pathToFramework})
set_source_files_properties(${pathToFramework} PROPERTIES
MACOSX_PACKAGE_LOCATION Frameworks
)
当项目构建共享框架时,情况会更加复杂,因为归档操作的框架位置与常规构建的框架位置不同。必须使用 Xcode 填充的变量,而不是直接提及目标。第一步是能够可靠地找到共享框架的构建位置,无论是常规构建还是归档操作。XcodeCONFIGURATION_BUILD_DIR项目变量指向框架最终所在的目录,因此可以像这样捕获其最终位置:
When the shared framework is built by the project, the situation is more
complicated because framework locations for an archive operation are different
to those of a regular build.
Variables populated by Xcode have to be used instead of mentioning the target
directly.
The first step is being able to reliably locate where the shared framework will
be built, whether for a regular build or an archive action.
The CONFIGURATION_BUILD_DIR Xcode project variable points to the directory in
which the framework will end up, so its final location can be captured like so:
add_library(MyFwk SHARED ...)
set(fwkLocation
"/\$(CONFIGURATION_BUILD_DIR)/MyFwk.framework"
)add_library(MyFwk SHARED ...)
set(fwkLocation
"/\$(CONFIGURATION_BUILD_DIR)/MyFwk.framework"
)
前面的前导斜杠 (/) 的原因
$(CONFIGURATION_BUILD_DIR)将在下面进一步讨论。
The reason for the leading forward slash (/) before
$(CONFIGURATION_BUILD_DIR) is discussed further below.
下一步是将框架作为源添加到可执行目标,并在原始框架目标上设置适当的依赖关系。以下定义了一个自定义命令,CMake 将其视为创建框架,但它是DEPENDS实际强制执行关系的部分:
The next step is to add the framework as a source to the executable target and
set up an appropriate dependency on the original framework target.
The following defines a custom command that CMake will treat as creating the
framework, but it is the DEPENDS part that actually enforces the
relationship:
add_custom_command(OUTPUT ${fwkLocation}
DEPENDS MyFwk
VERBATIM
)
add_executable(MyApp MACOSX_BUNDLE ... ${fwkLocation})add_custom_command(OUTPUT ${fwkLocation}
DEPENDS MyFwk
VERBATIM
)
add_executable(MyApp MACOSX_BUNDLE ... ${fwkLocation})
应该注意的是,只有当框架和可执行目标定义在同一目录范围中时,上述内容才有效。add_custom_command(OUTPUT)这是与使用该命令输出的目标交互方式的限制。
It should be noted that the above will only work when the framework and the
executable targets are defined in the same directory scope.
This is a limitation of how add_custom_command(OUTPUT) interacts with targets
that use the outputs of that command.
现在框架被视为可执行文件的源,可以使用与任何其他文件相同的方法将其放入捆绑包中。以下内容会将框架嵌入到 macOS 应用程序包的标准位置:
Now that the framework is seen as a source for the executable, the same method can be used to place it in the bundle as for any other file. The following will embed the framework in the standard location for a macOS app bundle:
set_source_files_properties(${fwkLocation} PROPERTIES
MACOSX_PACKAGE_LOCATION Frameworks
)set_source_files_properties(${fwkLocation} PROPERTIES
MACOSX_PACKAGE_LOCATION Frameworks
)
在上面的某些地方,CMake 尝试确定源文件是使用绝对路径还是相对路径给出的。其逻辑假定相对路径,除非路径以正斜杠开头,或者在某些情况下以生成器表达式开头。Xcode 提供的变量$(CONFIGURATION_BUILD_DIR)已经在需要时提供了完整路径,因此为了防止 CMake 在前面添加任何内容,在 之前包含一个前导斜杠
$(CONFIGURATION_BUILD_DIR)。这将在处理过程中使用的路径开头产生两个正斜杠,但所有涉及的工具都可以正确处理该问题。
In some places above, CMake attempts to work out whether the source file has
been given using an absolute or relative path.
Its logic assumes a relative path unless the path starts with a forward slash,
or in some cases a generator expression.
The $(CONFIGURATION_BUILD_DIR) variable provided by Xcode already supplies
the full path where needed, so in order to prevent CMake from prepending
anything, a leading forward slash is included before
$(CONFIGURATION_BUILD_DIR).
This will produce two forward slashes at the start of the path used during
processing, but all tools involved handle that without error.
对于外部提供的框架或项目构建的框架,可以通过为框架添加 Xcode 文件属性作为源文件属性来实现副本上的代码签名:
For externally provided frameworks or frameworks built by the project, code signing on copy can be achieved by adding an Xcode file attribute for the framework as a source file property:
set_source_files_properties(${fwkLocation} PROPERTIES
XCODE_FILE_ATTRIBUTES "CodeSignOnCopy"
)set_source_files_properties(${fwkLocation} PROPERTIES
XCODE_FILE_ATTRIBUTES "CodeSignOnCopy"
)
CMake 对权利的处理相当初级。它缺乏 Xcode IDE 在功能目标属性选项卡中提供的自动化功能,在该选项卡中打开特定功能还会负责添加任何所需的框架并根据需要自动更新应用程序 ID 详细信息。CMake 的支持仍然允许指定所有权利,但该过程完全是手动的。该项目负责以原始 plist 格式定义权利,并且还必须手动链接到任何所需的框架本身。尽管如此,权利的处理至少是可能的,而不会导致变通方法或步骤变得过于繁重。权利所需的任何框架都是系统提供的,因此不需要嵌入到应用程序中。
CMake’s handling of entitlements is fairly rudimentary. It falls short of the automation that the Xcode IDE provides in the Capabilities target properties tab, where turning on a particular capability also takes care of adding any required frameworks and automatically updates app ID details as needed. CMake’s support still allows all entitlements to be specified, but the process is entirely manual. The project is responsible for defining the entitlements in raw plist format and it must also manually link in any required frameworks itself. Nonetheless, the handling of entitlements is at least possible without the workarounds or steps becoming too burdensome. Any frameworks required by the entitlements are system-provided, so they do not need to be embedded with the application.
在更实际的日常层面上,对于并不总是显而易见的 CMake 行为,需要注意一点。使用 Xcode 生成器,当 CMake 编写 Xcode 项目时,它会创建一个名为 的实用程序目标
ZERO_CHECK。项目中的大多数其他目标都依赖于它ZERO_CHECK,其唯一目的是确定在执行其余构建之前是否需要重新运行 CMake。不幸的是,如果 CMake由重新运行ZERO_CHECK,则该构建的其余部分仍然使用旧的项目详细信息,这可能会由于使用过时的设置构建目标而导致细微的错误。第二次重建应始终确保任何此类错误构建的目标都能正确重建,但很容易错过。开发人员可能希望显式构建ZERO_CHECK目标,或者在修改
CMakeLists.txt文件或其他任何会导致 CMake 自动重新运行的内容后首先重新运行 CMake,或者简单地构建两次。
On a more practical, day-to-day level, a word of caution is in order regarding
a CMake behavior that isn’t always obvious. With the Xcode generator, when
CMake writes the Xcode project, it creates a utility target called
ZERO_CHECK. Most other targets in the project depend on ZERO_CHECK and
its sole purpose is to work out if CMake needs to be re-run before doing the
rest of the build. Unfortunately, if CMake is re-run by ZERO_CHECK, the
rest of that build still uses the old project details, which can result in
subtle errors due to targets being built with out-of-date settings. Rebuilding
a second time should always ensure any such incorrectly built targets are
rebuilt properly, but it can be easy to miss. Developers may want to explicitly
build the ZERO_CHECK target or re-run CMake first after modifying
CMakeLists.txt files or anything else that would cause CMake to be re-run
automatically, or simply build twice.
ZERO_CHECK如果项目包含对该命令的多次调用,则存在与存在的更微妙的问题project()。在第二次或以后的project()调用下面定义的目标可能没有ZERO_CHECK正确设置的依赖项。可以将该变量CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY设置为 true 以防止出现此问题,这也会产生加速 CMake 阶段的有用副作用,但仅在 CMake 3.11 中添加了对此变量的支持。
A more subtle problem related to ZERO_CHECK exists if the project contains
multiple calls to the project() command. Targets defined below the second or
later project() calls may not have their dependency on ZERO_CHECK set up
correctly. The CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY variable can
be set to true to prevent this problem, which will also have the useful side
effect of speeding up the CMake stage, but support for this variable was only
added in CMake 3.11.
CMake 能够处理针对 Apple 平台的项目,但需要仔细考虑其限制。如果应用程序包需要嵌入共享框架,则自 CMake 3.20 起可用于 Xcode 生成器的框架嵌入功能是迄今为止最简单的解决方案(如果针对 iOS、tvOS 或 watchOS,请使用 CMake 3.20.1 或更高版本以避免与 RPATH 相关的错误)。使用较旧的 CMake 版本需要更多的设置工作,并且涉及的步骤并不直观。如果不需要共享框架,那么只要使用 Xcode 生成器,CMake 从 3.14 开始的支持通常足以自动化该过程,而无需太多努力。但对于 Xcode 12 或更高版本,CMake 3.19.2 应被视为最低限度。其他生成器(例如 Makefiles 或 Ninja)非常适合构建未签名的 macOS 应用程序,但对于其他平台或签名的应用程序,这些生成器通常缺乏轻松生成最终分发包所需的一些功能。除了未签名的 macOS 应用程序开发之外,强烈建议使用 Xcode 生成器进行 Apple 开发。
CMake is able to handle projects targeting Apple platforms, but the limitations need to be considered carefully. If an app bundle needs to embed shared frameworks, the framework embedding features available for the Xcode generator since CMake 3.20 are by far the simplest solution (use CMake 3.20.1 or later to avoid an RPATH-related bug if targeting iOS, tvOS or watchOS). Using older CMake versions will require more work to set up and the steps involved are not intuitive. If shared frameworks are not needed, then CMake’s support from 3.14 onward is generally enough to automate the process without too much effort, again as long as the Xcode generator is used. For Xcode 12 or later though, CMake 3.19.2 should be considered the bare minimum. Other generators such as Makefiles or Ninja are fine for building an unsigned macOS application, but for other platforms or for signed applications, these generators typically lack some of the features needed to easily produce the final package for distribution. Except for unsigned macOS application development, use of the Xcode generator for Apple development is strongly advised.
当涉及到在 Apple 平台上使用 CMake 时,在线教程和示例中提供的许多信息都相对过时。特别是,在 iOS 上看到相当复杂的工具链文件是很常见的,但此类工具链文件中包含的大部分逻辑要么是有问题的,要么应该移至项目本身。强烈鼓励开发人员将最低 CMake 版本设置为不低于 3.14,并利用对 Apple 平台改进的支持。这使得工具链文件变得更加简单甚至不必要——只需设置
CMAKE_SYSTEM_NAME为iOS,tvOS或watchOS通常就足够了。与 Xcode 项目设置、特定于设备或平台的配置等相关的逻辑应该放在项目本身中。
Much of the information available in online tutorials and examples is
relatively out of date when it comes to using CMake for Apple platforms.
In particular, it is very common to see fairly complex toolchain files for iOS,
but much of the logic contained in such toolchain files is either questionable
or should be moved to the project itself.
Developers are strongly encouraged to set their minimum CMake version no lower
than 3.14 and take advantage of the improved support for Apple platforms.
This makes toolchain files vastly simpler or even unnecessary — just setting
CMAKE_SYSTEM_NAME to iOS, tvOS or watchOS would often be sufficient.
Logic related to Xcode project settings, device- or platform-specific
configuration, etc. should go in the project itself.
教程和示例经常做的事情之一是通过设置CMAKE_OSX_ARCHITECTURES变量来指定目标架构。当将 Xcode 生成器与针对 iOS、watchOS 或 tvOS 的项目一起使用时,这可能会阻止开发人员在设备和模拟器版本之间自由切换。在 Xcode IDE 中工作或在命令行构建时,可以在构建时选择目标架构。项目通常应避免设置CMAKE_OSX_ARCHITECTURES特定的架构列表。相反,让 Xcode 根据所选 SDK 提供标准架构集。CMAKE_OSX_ARCHITECTURES使用 Xcode 生成器时,可以通过设置为 来实现
$(ARCHS_STANDARD)。如果项目想要指定架构以方便开发人员,则应将其作为缓存变量来执行,而不是强制覆盖现有值。这将确保开发商仍然拥有控制权。
One of the things that tutorials and examples often do is specify the target
architecture by setting the CMAKE_OSX_ARCHITECTURES variable.
When using the Xcode generator with projects targeting iOS, watchOS or tvOS,
this can prevent the developer from being able to switch freely between device
and simulator builds.
The target architecture is selectable at build time when working in the Xcode
IDE or when building at the command line.
Projects should generally avoid setting CMAKE_OSX_ARCHITECTURES to a
specific list of architectures.
Instead, let Xcode supply the standard set of architectures based on the
selected SDK.
When using the Xcode generator, this can be achieved by setting
CMAKE_OSX_ARCHITECTURES to $(ARCHS_STANDARD).
If a project wants to specify the architectures for the convenience of its
developers, it should do so as a cache variable and not force overwriting an
existing value.
This will ensure that developers still have control.
SDK的选择最终由 的值决定CMAKE_OSX_SYSROOT。使用 CMake 3.14 或更高版本时,最好通过 选择平台
CMAKE_SYSTEM_NAME,因为这将为相应的设备 SDK 设置CMAKE_OSX_SYSROOT
合适的默认值。如果无法避免使用 CMake 3.13 或更早版本,则开发人员必须在 中指定 SDK CMAKE_OSX_SYSROOT,它通常应该是 iOS 构建的通用设备字符串iphoneos。当选择设备 SDK 时,Xcode 会识别匹配的模拟器,因此设备和模拟器版本都可供开发人员使用。
SDK selection is ultimately determined by the value of CMAKE_OSX_SYSROOT.
When using CMake 3.14 or later, prefer to select the platform via
CMAKE_SYSTEM_NAME, as this will take care of setting CMAKE_OSX_SYSROOT
to a suitable default value for the corresponding device SDK.
If using CMake 3.13 or earlier cannot be avoided, the developer has to specify
the SDK in CMAKE_OSX_SYSROOT, which should typically be the generic device
string iphoneos for iOS builds.
Xcode recognizes a matching simulator when a device SDK is chosen,
so the device and simulator builds will both be available to the developer.
虽然可以将 SDK 版本指定为 给定值的一部分
CMAKE_OSX_SYSROOT,但通常没有理由这样做。更有可能的是,应该通过
MACOSX_DEPLOYMENT_TARGET或XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET设置 SDK 版本来设置部署目标。部署目标最终决定应用程序是否能够在目标上运行,这与用于构建它的 SDK 无关(当然假设 SDK 支持该部署目标)。由于默认情况下将使用 SDK 的最新可用版本,因此要求构建使用特定 SDK 版本几乎没有什么好处,甚至可能有害。当指定特定的 SDK 版本时,并非所有开发人员计算机都可用它,因为它取决于所使用的 Xcode 版本。一些开发人员将较旧的 SDK 移植到较新的 Xcode 版本以尝试解决此问题,但这不是必需的,而且我们强烈建议不要这样做。
While it is possible to specify the SDK version as part of the value given to
CMAKE_OSX_SYSROOT, there is usually little reason to do so.
It is much more likely that the deployment target should be set via
MACOSX_DEPLOYMENT_TARGET or XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET than
the SDK version be set.
It is the deployment target that ultimately determines whether the application
will be able to run on the target and this is independent of the SDK used to
build it (assuming the SDK supports that deployment target, of course).
Since the latest available version of the SDK will be used by default, there is
little to be gained by requiring the build to use a specific SDK version and it
can even be harmful.
When a particular SDK version is specified, not all developer machines may have
it available, since it will depend on which Xcode version is being used.
Some developers carry over older SDKs to newer Xcode versions to try to work
around this, but that should not be necessary and is actively discouraged.
一些示例还设置CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH为 true,以便仅在 Xcode IDE 中构建当前选定的架构。同样,这个决定通常应该由开发人员在构建时决定,而不是由项目强制执行。默认值CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH取决于是否CMAKE_OSX_ARCHITECTURES已显式设置,但通常开发人员可以/应该根据自己的需要进行设置。他们可以在 Xcode IDE 的项目内执行此操作,也可以在命令行上-arch使用
xcodebuild. 可以多次指定该-arch选项以构建多个架构,并xcodebuild自动为这些架构生成通用二进制文件。
Some examples also set CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH to true so
as to only build the currently selected architecture in the Xcode IDE.
Again, this is a decision that should typically be left up to the developer at
build time rather than being enforced by the project.
The default value of CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH depends on
whether or not CMAKE_OSX_ARCHITECTURES has been explicitly set, but in
general the developer can/should set this according to their own needs.
They can do so within the project in the Xcode IDE, or they can override the
project setting when building on the command line with the -arch option to
xcodebuild.
The -arch option can be specified multiple times to build more than one
architecture and have xcodebuild generate universal binaries for those
architectures automatically.
将构建限制为仅一种架构可能有意义的一种情况是项目包含链接到不提供通用二进制文件的库或框架的目标(即它们仅针对单个目标平台构建)。在这种情况下,由于这些库或框架仅支持单个平台,因此只能为该平台构建项目。类似地,当使用find_library()or (第 24 章“查找事物”find_package()中介绍
)时,这些命令本质上假设它们是为单个平台构建的,因此它们不会尝试以支持在多个目标平台之间切换的方式定义它们找到的事物。
One situation where it may make sense to restrict the build to just one
architecture is where the project contains targets that link to libraries or
frameworks that do not provide universal binaries (i.e. they were only built
for a single target platform).
In this case, since those libraries or frameworks only support a single
platform, the project can only be built for that platform.
Similarly, when using find_library() or find_package() (covered in
Chapter 24, Finding Things), these commands inherently assume they are building for a
single platform, so they do not attempt to define the things they find in a way
that supports switching between multiple target platforms.
有些项目可能会选择使用 CMake 的安装功能,而不是假设 Xcode 在构建时完成了可分发包所需的一切。对于这种情况,IOS_INSTALL_COMBINED可以将 target 属性设置为 true 以构建目标的设备版本和模拟器版本,并在安装步骤期间将它们组合成单个通用二进制文件。缓存CMAKE_IOS_INSTALL_COMBINED变量是在项目范围内启用此行为的更方便的方法。
Some projects may choose to use CMake’s install functionality rather than
assume Xcode does everything needed for a distributable bundle at build time.
For such cases, the IOS_INSTALL_COMBINED target property can be set to
true to build both device and simulator versions of the target and to combine
them into a single universal binary during the install step.
The CMAKE_IOS_INSTALL_COMBINED cache variable is a more convenient way to
enable this behavior project-wide.
Xcode 的构建输出可能非常冗长,因此开发人员可能会选择使用诸如xcpretty隐藏大量细节之类的工具(这对于脚本化构建来说更常见,以减少日志大小)。不幸的是,这个特定的工具通常会隐藏任何 CMake 的自定义构建后步骤的输出,即使这些自定义步骤会导致构建错误。当此类自定义步骤失败时,很难找出失败的原因,因此建议避免使用此工具,或者至少可以轻松地在脚本中将其关闭,以帮助诊断构建问题。该命令-quiet的选项xcodebuild可能是减少日志输出而不隐藏警告或错误的替代方法,但它也可能隐藏太多细节。
The build output from Xcode can be quite verbose, so developers may choose to
use a tool like xcpretty to hide much of the detail (this is more common for
scripted builds to reduce log sizes). Unfortunately, this particular tool will
typically hide the output of any of CMake’s custom post-build steps, even if
those custom steps cause a build error. When such custom steps fail, it can
therefore be very difficult to work out the cause of the failure, so it is
advisable to either avoid the use of this tool or at least make it easy to
switch it off in scripts to help diagnose build problems. The -quiet option
to the xcodebuild command may be an alternative to reduce log output without
hiding warnings or errors, but it may also hide too much detail.
对于少数幸运者来说,一个项目可能独立于其他任何东西,只需要满足轻微的质量限制,或者对于一次性实验来说可能根本不需要满足。更可能的情况是,在某个时刻,项目需要超越其自身孤立的存在并与外部实体进行交互。这发生在两个方向:
For the lucky few, a project may be independent of anything else and only needs to satisfy mild quality constraints or perhaps none at all for throw-away experiments. The more likely scenario is that, at some point, the project needs to move beyond its own isolated existence and interact with external entities. This occurs in two directions:
将项目作为独立包提供或供其他项目使用也会带来一定质量水平的期望。自动化测试通常是任何强大的软件交付策略的关键部分,这意味着必须易于定义和执行测试以及报告结果。
Making a project available either as a standalone package or for consumption by other projects also brings an expectation of a certain level of quality. Automated testing is usually a critical part of any robust software delivery strategy, which means it must be easy to define and execute tests and also to report on the results.
CMake 工具套件为上述所有内容提供帮助。它提供了在较低级别运行的命令,用于查找单个文件、库等,并且还提供了基于这些命令构建的模块,以便为依赖关系管理提供更高级别的入口点。CTest 框架提供了一组丰富的自动化测试功能,而 CPack 则大大简化了创建各种格式的包的过程。本书的这一部分涵盖了这些外部关注的主题,展示了如何充分利用 CMake 提供的功能,同时还强调了常见的错误和陷阱。
The CMake suite of tools provides assistance with all of the above. It provides commands that operate at a lower level for finding individual files, libraries, etc. and it also provides modules that build on these commands to give a higher level entry point for dependency management. The CTest framework provides a rich set of automated testing capabilities, while CPack considerably eases the process of creating packages in various formats. This part of the book covers these externally focused topics, showing how to get the most out of what CMake offers while also highlighting common mistakes and pitfalls.
在本书这一部分的末尾,有两章让读者重新思考如何组织一个项目。做好这一点需要了解构建级别的功能以及项目如何与其他项目交互。借助从本书前面几章中获得的知识,这两章展示了如何构建和定义一个灵活、健壮且更易于开发人员使用的项目。提出了多种可以显着提高整体构建性能的技术。
Towards the end of this part of the book, two chapters bring the reader full circle back to thinking about how to organize a project. Doing this well requires an appreciation for both the build level features and how a project will interact with other projects. With the benefit of the knowledge gained from the earlier chapters of the book, these two chapters show how to structure and define a project to be flexible, robust and easier for developers to work with. A variety of techniques are presented which can significantly improve the overall build performance.
本书的最后一章涵盖了在 Qt 项目中使用 CMake 的专题。它借鉴了本书中介绍的许多技术和功能,并涵盖了 CMake 和 Qt 提供的特定于 Qt 的功能。
The final chapter of the book covers the special topic of using CMake with Qt projects. It draws on many of the techniques and features introduced throughout the book and covers Qt-specific features provided by both CMake and Qt.
一个至少中等规模的项目可能会依赖于项目本身之外的东西提供的东西。例如,它可能期望特定的库或工具可用,或者它可能需要知道它使用的库的特定配置或头文件的位置。在更高的层面上,项目可能希望找到一个完整的包,该包可能定义一系列内容,包括目标、函数、变量以及常规 CMake 项目可能定义的任何其他内容。
A project of at least modest size will likely rely on things provided by something outside the project itself. For example, it may expect a particular library or tool to be available, or it may need to know the location of a specific configuration or header file for a library it uses. At a higher level, the project may want to find a complete package that potentially defines a range of things including targets, functions, variables and anything else a regular CMake project might define.
为了帮助实现这一点,CMake 提供了各种功能,允许项目查找各种内容并使自己易于查找并合并到其他项目中。各种find_…()命令提供了搜索特定文件、库或程序,甚至整个包的能力。CMake 模块还添加了用于提供有关外部包的信息的功能pkg-config,而其他模块则有助于编写包文件以供其他项目使用。本章介绍 CMake 对搜索文件系统上已有内容的支持。下载缺少的依赖项的能力在
第 28 章“外部内容”中介绍,并在第 26.8 节“编写配置包文件”中解决了准备项目以供其他项目找到的问题。
To assist with this, CMake provides a variety of features which allow projects
to find various things and to make themselves easy to find and be
incorporated into other projects. Various find_…() commands provide the
ability to search for specific files, libraries or programs, or indeed for an
entire package. CMake modules also add the ability to use pkg-config to
provide information about external packages, while other modules facilitate
writing package files for other projects to consume. This chapter covers
CMake’s support for searching for something already available on the file
system. The ability to download missing dependencies is covered in
Chapter 28, External Content and preparing a project for being found by other projects
is addressed in Section 26.8, “Writing A Config Package File”.
搜索某物的基本思想相对简单,但显而易见的是,如何进行搜索的细节可能相当复杂。在许多情况下,默认行为是适当的,但了解搜索位置及其顺序可以允许项目定制搜索以考虑非标准行为和异常情况。
The basic idea of searching for something is relatively straightforward, but as will become apparent, the details of how the search is conducted can be quite involved. In many cases, the default behaviors are appropriate, but an understanding of the search locations and their ordering can allow projects to tailor the search to account for non-standard behaviors and unusual circumstances.
从概念上讲,最基本的搜索任务是查找特定文件,而实现此目的最直接的方法是使用命令find_file()。它还可以很好地介绍整个find_…()命令系列,因为它们都共享许多相同的选项并具有相似的行为。该命令的完整语法如下:
Conceptually, the most basic search task is to find a specific file and the
most direct way to achieve this is with the find_file() command. It also
serves as a good introduction to the whole family of find_…() commands,
since they all share many of the same options and have similar behavior. The
full syntax of this command is as follows:
find_file(outVar
name | NAMES name1 [name2...]
[HINTS path1 [path2...] [ENV var]...]
[PATHS path1 [path2...] [ENV var]...]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_SYSTEM_PATH]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
[DOC "description"]
[REQUIRED] # CMake 3.18 or later
[NO_CACHE] # CMake 3.21 or later
)find_file(outVar
name | NAMES name1 [name2...]
[HINTS path1 [path2...] [ENV var]...]
[PATHS path1 [path2...] [ENV var]...]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_SYSTEM_PATH]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
[DOC "description"]
[REQUIRED] # CMake 3.18 or later
[NO_CACHE] # CMake 3.21 or later
)
该命令可以搜索单个文件名,也可以使用选项为其提供名称列表NAMES。当要搜索的文件名称可能有一些变化时,列表会很有用,例如不同的操作系统发行版选择不同的命名约定、是否合并版本号、考虑文件名称从一个版本更改为另一个版本等。在。名称应按首选顺序列出,因为搜索将在找到的第一个名称处停止(在转到下一个名称之前,将检查完整的搜索位置集是否有特定名称)。当指定包含某种形式的版本编号的名称时,CMake 文档建议在包含版本号的名称之前列出不带版本详细信息的名称,以便本地构建的文件更有可能在操作系统提供的文件之前找到。
The command can search for a single file name or it can be given a list of
names with the NAMES option. A list can be useful when the file being
searched for may have a few variations on its name, such as different operating
system distributions choosing different naming conventions, incorporating
version numbers or not, accounting for a file changing names from one release
to another and so on. The names should be listed in preferred order, since the
search will stop at the first one found (the complete set of search locations
is checked for a particular name before moving on to the next name). When
specifying names that contain some form of version numbering, the CMake
documentation recommends listing the name(s) without version details ahead of
those that do so that locally built files are more likely to be found ahead of
files provided by the operating system.
搜索将根据明确的顺序在一组检查的位置上进行。大多数位置都有一个关联选项,如果存在该选项,将导致该位置被跳过,从而允许根据需要定制搜索。下表总结了搜索顺序:
The search will be conducted over a set of locations checked according to a well-defined order. Most locations have an associated option which will cause that location to be skipped if the option is present, thereby allowing the search to be tailored as needed. The following table summarizes the search order:
| 地点 | 跳过选项 |
|---|---|
包根变量 Package root variables |
|
缓存变量(特定于 CMake) Cache variables (CMake-specific) |
|
环境变量(特定于 CMake) Environment variables (CMake-specific) |
|
通过 Paths specified via the |
|
环境变量(系统特定) Environment variables (system-specific) |
|
缓存变量(特定于平台) Cache variables (platform-specific) |
|
通过 Paths specified via the |
find_file()从作为调用一部分调用的脚本调用时find_package()(本章稍后讨论)。它最初在 CMake 3.9.0 中添加为搜索位置,但由于向后兼容性问题在 3.9.1 中被删除。然后在 CMake 3.12 中再次重新添加它并解决了问题。有关此搜索位置的进一步讨论将在第 24.5 节“查找包”中进行,
其中其使用更为相关。
find_file() is invoked from
a script invoked as part of a find_package() call (discussed later in this
chapter).
It was initially added as a search location in CMake 3.9.0, but was removed in
3.9.1 due to backward compatibility issues.
It was then re-added again in CMake 3.12 with the problems addressed.
Further discussion of this search location is deferred to Section 24.5, “Finding Packages”
where its use is more relevant.
CMAKE_PREFIX_PATH、CMAKE_INCLUDE_PATH和
CMAKE_FRAMEWORK_PATH。其中,CMAKE_PREFIX_PATH也许是最方便的,因为设置它不仅适用于find_file(),而且适用于所有其他find_…()命令。它表示一个基点,在该基点下预计会出现典型的目录结构bin、等,并且每个lib命令都会附加其自己的子目录来构造搜索路径。在 的情况下,对于 中的每个条目,将搜索目录。如果设置了该
变量,则将首先搜索特定于体系结构的目录
,以确保特定于体系结构的位置优先于通用位置。该变量通常由 CMake 自动设置,项目通常不应尝试自行设置它。includefind_…()find_file()CMAKE_PREFIX_PATH<prefix>/includeCMAKE_LIBRARY_ARCHITECTURE<prefix>/include/${CMAKE_LIBRARY_ARCHITECTURE}CMAKE_LIBRARY_ARCHITECTURE
对于需要搜索更具体的包含或框架路径并且它不是标准目录布局或包的一部分的情况,
可以使用CMAKE_INCLUDE_PATH和变量。CMAKE_FRAMEWORK_PATH它们各自提供要搜索的目录列表,但与 不同的是
CMAKE_PREFIX_PATH,不include附加子目录。
受和CMAKE_INCLUDE_PATH支持,而
受这两个命令 和 支持
。除此之外,这两组路径的处理方式相同。有关更多详细信息,请参阅下文第 24.1.1 节“Apple 特定行为” 。find_file()find_path()CMAKE_FRAMEWORK_PATHfind_library()
使用 CMake 3.16 或更高版本时,该CMAKE_FIND_USE_CMAKE_PATH变量可用于控制是否在搜索中考虑 CMake 特定的缓存变量的默认行为。如果该变量未定义或设置为 true,则搜索将考虑缓存变量。如果设置为 false,搜索将忽略它们。
CMAKE_PREFIX_PATH, CMAKE_INCLUDE_PATH and
CMAKE_FRAMEWORK_PATH. Of these, CMAKE_PREFIX_PATH is perhaps the most
convenient, as setting it works not just for find_file(), but also for all
the other find_…() commands. It represents a base point below which a
typical directory structure of bin, lib, include and so on is expected
and each find_…() command appends its own subdirectory to construct search
paths. In the case of find_file(), for each entry in CMAKE_PREFIX_PATH, the
directory <prefix>/include will be searched. If the
CMAKE_LIBRARY_ARCHITECTURE variable is set, then the
architecture-specific directory
<prefix>/include/${CMAKE_LIBRARY_ARCHITECTURE} will be searched first to
ensure architecture-specific locations take precedence over generic locations.
The CMAKE_LIBRARY_ARCHITECTURE variable is normally set automatically by
CMake and projects should not generally try to set it themselves.
For the cases where a more specific include or framework path needs to be
searched and it is not part of a standard directory layout or package, the
CMAKE_INCLUDE_PATH and CMAKE_FRAMEWORK_PATH variables can be used. They
each provide a list of directories to be searched, but unlike
CMAKE_PREFIX_PATH, no include subdirectory is appended.
CMAKE_INCLUDE_PATH is supported by find_file() and find_path(), whereas
CMAKE_FRAMEWORK_PATH is supported by those two commands and by
find_library(). Other than that, these two sets of paths are handled in the
same way. See Section 24.1.1, “Apple-specific Behavior” further below for additional details.
When using CMake 3.16 or later, the CMAKE_FIND_USE_CMAKE_PATH variable
can be used to control the default behavior of whether to consider
CMake-specific cache variables in the search.
If the variable is not defined or is set to true, the search will consider the
cache variables.
If set to false, the search will ignore them.
CMAKE_PREFIX_PATH、
CMAKE_INCLUDE_PATH和CMAKE_FRAMEWORK_PATH的处理方式与同名缓存变量相同,不同之处在于,在 Unix 平台上,每个列表项将用冒号 (:) 而不是分号 (;) 分隔。这是为了允许环境变量使用与每个平台的其他路径列表相同样式定义的特定于平台的路径列表。
使用 CMake 3.16 或更高版本时,CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH
可以使用该变量以与
CMAKE_FIND_USE_CMAKE_PATH.
CMAKE_PREFIX_PATH,
CMAKE_INCLUDE_PATH and CMAKE_FRAMEWORK_PATH are treated in the same way as
the same-named cache variables, except that on Unix platforms, each list item
will be separated by a colon (:) instead of a semi-colon (;). This is to allow
the environment variables to use platform-specific path lists defined in the
same style as other path lists for each platform.
When using CMake 3.16 or later, the CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH
variable can be used to control the default behavior in a similar way as for
CMAKE_FIND_USE_CMAKE_PATH.
INCLUDE和PATH。两者都可能包含一个由特定于平台的路径分隔符(Unix 上的冒号,Windows 上的分号)分隔的列表,每个项目都被添加到搜索位置集中(INCLUDE在之前添加PATH)。
仅在 Windows(包括 Cygwin)上,PATH条目将以更复杂的方式进一步处理。对于环境变量中的每个项目,将通过删除末尾的任何尾随目录或子目录PATH来计算基本路径。如果已定义,
则添加。之后,无论是否已定义,该路径都会添加到搜索路径集中。在搜索路径排序中,这些路径紧邻未修改的项目本身放置在前面。例如,如果设置为并且
环境变量 contains ,则将添加以下有序的搜索路径集:binsbinCMAKE_LIBRARY_ARCHITECTURE<base>/include/${CMAKE_LIBRARY_ARCHITECTURE}<base>/includeCMAKE_LIBRARY_ARCHITECTUREPATHCMAKE_LIBRARY_ARCHITECTUREsomearchPATHC:\foo\bin;D:\bar
C:\foo\include\somearch
C:\foo\include
C:\foo\bin
D:\bar\include\somearch
D:\bar\include
D:\bar
使用 CMake 3.16 或更高版本时,CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH
可以使用该变量以与
CMAKE_FIND_USE_CMAKE_PATH.
INCLUDE and PATH.
Both may contain a list separated by the platform-specific path separator
(colon on Unix, semi-colon on Windows), with each item being added to
the set of search locations (INCLUDE is added before PATH).
On Windows only (including Cygwin), the PATH entries will be further
processed in a more complex manner.
For each item in the PATH environment variable, a base path will be
computed by dropping any trailing bin or sbin subdirectory from the end.
If CMAKE_LIBRARY_ARCHITECTURE is defined,
<base>/include/${CMAKE_LIBRARY_ARCHITECTURE} is added.
After that, the <base>/include path is added to the set of search paths
regardless of whether CMAKE_LIBRARY_ARCHITECTURE is defined.
In the search path ordering, these paths are placed immediately before the
unmodified PATH item itself.
For example, if CMAKE_LIBRARY_ARCHITECTURE was set to somearch and the
PATH environment variable contained C:\foo\bin;D:\bar, the following
ordered set of search paths would be added:
C:\foo\include\somearch
C:\foo\include
C:\foo\bin
D:\bar\include\somearch
D:\bar\include
D:\bar
When using CMake 3.16 or later, the CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH
variable can be used to control the default behavior in a similar way as for
CMAKE_FIND_USE_CMAKE_PATH.
CMAKE_SYSTEM_PREFIX_PATH、
CMAKE_SYSTEM_INCLUDE_PATH和CMAKE_SYSTEM_FRAMEWORK_PATH。这些特定于平台的变量并不是由项目或开发人员设置的。相反,它们是由 CMake 自动设置的,作为设置平台工具链的一部分,以便它们反映特定于所使用的平台和编译器的位置。例外情况是开发人员提供自己的工具链文件,在这种情况下,在工具链文件中设置这些变量可能是合适的。
使用 CMake 3.16 或更高版本时,CMAKE_FIND_USE_CMAKE_SYSTEM_PATH
可以使用该变量以与
CMAKE_FIND_USE_CMAKE_PATH.
CMAKE_SYSTEM_PREFIX_PATH,
CMAKE_SYSTEM_INCLUDE_PATH and CMAKE_SYSTEM_FRAMEWORK_PATH. These
platform-specific variables are not intended to be set by the project or the
developer. Rather, they are set automatically by CMake as part of setting up
the platform toolchain so that they reflect locations specific to the platform
and compilers being used. The exception to this is where a developer provides
their own toolchain file, in which case it may be appropriate to set these
variables within the toolchain file.
When using CMake 3.16 or later, the CMAKE_FIND_USE_CMAKE_SYSTEM_PATH
variable can be used to control the default behavior in a similar way as for
CMAKE_FIND_USE_CMAKE_PATH.
HINTS和PATHS
HINTS and PATHS
HINTS和PATHS选项是项目本身应该注入额外搜索路径的地方。HINTS和 之间的主要区别PATHS在于,PATHS通常是永远不会改变且不依赖于任何其他内容的固定位置,而HINTS
通常是根据其他值计算的,例如先前已找到的某些内容的位置或依赖于变量或属性值的路径。
PATHS是最后搜索的目录,但HINTS在任何特定于平台或系统的位置之前进行搜索。
和HINTS都PATHS支持指定环境变量,其中可能包含主机本机格式的路径列表(即,Unix 系统上以冒号分隔,Windows 上以分号分隔)。这是通过在环境变量名称前面加上 来完成的ENV,例如PATHS ENV FooDirs。
HINTS and PATHS options are
where the project itself should inject additional search paths. The main
difference between HINTS and PATHS is that PATHS are generally fixed
locations that never change and don’t depend on anything else, whereas HINTS
are usually computed from other values, such as the location of something
already found previously or a path dependent on a variable or property value.
PATHS are the last directories searched, but HINTS are searched before any
platform- or system-specific locations.
Both HINTS and PATHS support specifying environment variables which may
contain a list of paths in the host’s native format (i.e. colon-separated for
Unix systems, semi-colon separated on Windows). This is done by preceding the
name of the environment variable with ENV, such as PATHS ENV FooDirs.
除了HINTS和PATHS搜索位置之外的所有位置都有一个关联的跳过选项NO_…_PATH,可用于仅跳过该组位置。此外,该NO_DEFAULT_PATH选项可用于绕过除
HINTS和位置之外的所有PATHS位置,强制命令仅搜索项目控制的特定位置。这些NO_…选项会覆盖变量提供的任何默认值CMAKE_FIND_USE_…
。
All but the HINTS and PATHS search locations have an associated skip option
of the form NO_…_PATH which can be used to skip just that set of locations.
In addition, the NO_DEFAULT_PATH option can be used to bypass all but the
HINTS and PATHS locations, forcing the command to search just specific
places controlled by the project.
These NO_… options override any default provided by CMAKE_FIND_USE_…
variables.
该PATH_SUFFIXES选项可用于提供附加子目录列表以在每个搜索位置下方进行检查。每个搜索位置依次与每个后缀一起使用,然后在转到下一个搜索位置之前根本不使用任何后缀。请谨慎使用此选项,因为它会极大地扩展要搜索的位置总数。
The PATH_SUFFIXES option can be used to provide a list of additional
subdirectories to check below each search location. Each search location is
used with each suffix in turn, then without any suffix at all before moving on
to the next search location. Use this option with care, as it greatly expands
the total number of locations to be searched.
在许多情况下,项目只需要指定要搜索的单个文件名,并且搜索顺序的复杂性并不是特别令人感兴趣。也许只需要提供一些额外的搜索路径(相当于选项PATHS)。在这种情况下,可以使用该命令的较短形式:
In many cases, projects only need to specify a single file name to search for
and the complexities of the search order are not of particular interest.
Perhaps just a few additional paths to search might need to be provided
(equivalent to the PATHS option). In such cases, a shorter form of the
command can be used:
find_file(outVar name [path1 [path2...]])find_file(outVar name [path1 [path2...]])
无论使用短形式还是长形式,搜索位置的排序都被设计为在更具体的位置之前搜索更通用的位置。虽然这通常是期望的行为,但在某些情况下可能并非如此。
Whether the short or long form is used, the ordering of the search locations is designed to search in more specific locations ahead of more generic ones. While this is typically the desired behavior, there may be situations where this is not the case.
例如,项目可能希望始终首先在通过缓存或环境变量提供的任何搜索位置之前查找特定路径。find_file()项目可以通过使用控制搜索位置的不同选项多次调用来强制执行不同的优先级
。一旦找到文件,该位置就会被缓存,所有后续调用都将跳过搜索。这是各种NO_…_PATH选项最有用的地方。例如,以下内容首先强制在位置中搜索
/opt/foo/include,并且仅当未找到时才会搜索完整的默认位置集:
For example, a project may wish to always look in specific paths
first ahead of any search locations provided through cache or environment
variables. Projects can enforce a different priority by calling
find_file() multiple times with different options controlling the search
locations. Once the file is found, the location is cached and all subsequent
calls will skip their search. This is where the various NO_…_PATH options
are most useful. For example, the following enforces searching in the location
/opt/foo/include first and only if not found there will the full set of
default locations be searched:
find_file(FOO_HEADER foo.h
PATHS /opt/foo/include
NO_DEFAULT_PATH
)
find_file(FOO_HEADER foo.h)find_file(FOO_HEADER foo.h
PATHS /opt/foo/include
NO_DEFAULT_PATH
)
find_file(FOO_HEADER foo.h)
其工作的一个重要要求是每次调用必须使用相同的结果变量。一旦找到文件,就会设置该变量并控制跳过后续调用。该DOC选项可用于指定存储结果的缓存变量的文档,但项目经常忽略它。选择自记录的变量名应该不需要显式记录。按照惯例,这些变量通常都是大写,并使用下划线分隔单词。
An important requirement for this to work is that the same result variable must
be used for each call.
It is that variable that gets set and that controls skipping subsequent calls
once the file has been found.
The DOC option can be used to specify documentation for the cache variable
storing the result, but projects frequently omit it.
Choosing a variable name that is self-documenting should make the need for
explicit documentation unnecessary.
By convention, these variables are usually all uppercase and use underscores
to separate words.
使用 CMake 3.20 或更早版本时,与结果变量相关的某些极端情况可能会产生令人惊讶的行为。find_file()当调用之前已经存在与结果变量同名的非缓存变量时,必须小心。非缓存变量可能会被忽略,具体取决于缓存变量是否也存在以及此类缓存变量是否具有类型(请参阅第 5.3 节“缓存变量”)。CMake 3.21 添加了策略CMP0125,它确保非缓存变量的处理方式更像人们直观期望的方式。CMake 3.21 中还添加了该NO_CACHE选项,作为将结果仅分配给非缓存变量的方法,但项目通常应避免使用它。
通过强制在每次运行时始终重复搜索,NO_CACHE会对配置阶段的性能产生不利影响
。find_file()
When using CMake 3.20 or earlier, certain corner cases related to the result
variable may yield surprising behavior.
Care must be exercised when a non-cache variable with the same name as the
result variable already exists before find_file() is called.
The non-cache variable might be ignored, depending on whether a cache variable
also exists and whether such a cache variable has a type
(see Section 5.3, “Cache Variables”).
CMake 3.21 added policy CMP0125, which ensures that a non-cache variable will
be handled more like how one would intuitively expect.
The NO_CACHE option was also added in CMake 3.21 as a way to assign the
result only to a non-cache variable, but projects should generally avoid using
it.
NO_CACHE adversely affects the performance of the configure stage by forcing
find_file() to always repeat the search on every run.
如果找不到该文件,则在表达式中使用存储在变量中的值将求值为 false if()。只需要在最终调用后进行测试find_file()。
If the file could not be found, the value stored in the variable will evaluate
to false if used in an if() expression.
The test only needs to be made after the final call to find_file().
find_file(FOO_HEADER foo.h
PATHS /opt/foo/include
NO_DEFAULT_PATH
)
find_file(FOO_HEADER foo.h)
if(NOT FOO_HEADER)
message(FATAL_ERROR "Could not find foo.h")
endif()find_file(FOO_HEADER foo.h
PATHS /opt/foo/include
NO_DEFAULT_PATH
)
find_file(FOO_HEADER foo.h)
if(NOT FOO_HEADER)
message(FATAL_ERROR "Could not find foo.h")
endif()
上面的形式适用于任何 CMake 版本,但是从 CMake 3.18 开始,REQUIRED
可以使用该选项来更简洁地表达逻辑:
The above form works for any CMake version, but from CMake 3.18, the REQUIRED
option can be used to express the logic more concisely:
find_file(FOO_HEADER foo.h
PATHS /opt/foo/include
NO_DEFAULT_PATH
)
find_file(FOO_HEADER foo.h REQUIRED)find_file(FOO_HEADER foo.h
PATHS /opt/foo/include
NO_DEFAULT_PATH
)
find_file(FOO_HEADER foo.h REQUIRED)
尽管该find_file()命令可用于查找任何文件,但它的起源是搜索头文件。这就是为什么某些默认搜索路径include附加了子目录的原因。在 Apple 平台上,框架有时包含自己的头文件(请参阅第 23.3 节“框架”),并且该
find_file()命令具有与在其中的相应子目录中搜索相关的其他行为。对于每个搜索位置,该命令可以将该位置视为框架、普通目录或两者。该行为由变量控制CMAKE_FIND_FRAMEWORK,该变量应包含以下值之一:
Although the find_file() command can be used to find any file, it has its
origins in searching for header files. This is why some of the default search
paths have an include subdirectory appended. On Apple platforms, frameworks
sometimes contain their own header files (see Section 23.3, “Frameworks”) and the
find_file() command has additional behaviors related to searching in the
appropriate subdirectories within them. For each search location, the command
may treat the location as a framework, as an ordinary directory or both. The
behavior is controlled by the CMAKE_FIND_FRAMEWORK variable, which is
expected to hold one of the following values:
FIRST
FIRST
LAST
LAST
ONLY
ONLY
NEVER
NEVER
FIRST意味着将搜索位置视为框架的顶级目录,并附加适当的子目录以下降到其中的位置
Headers。如果在那里找不到指定的文件,则搜索位置将被视为普通目录而不是框架并再次搜索。LAST颠倒该顺序,ONLY不会将该位置视为普通目录,并且NEVER会跳过将该位置视为框架的步骤。Apple 系统的默认设置是FIRST,这通常是所需的行为。
FIRST means to treat the search location as though it was the top directory
of a framework and to append the appropriate subdirectories to descend into the
Headers location within it. If the named file cannot be found there, then the
search location is treated as an ordinary directory rather than a framework and
searched again. LAST reverses that order, ONLY will not treat the location
as an ordinary directory and NEVER will skip the step that treats the
location as a framework. The default for Apple systems is FIRST, which is
usually the desired behavior.
对于交叉编译场景,搜索位置集变得相当复杂。交叉编译工具链通常收集在自己的目录结构下,以使其与默认主机工具链分开。在搜索特定文件时,通常需要首先在主机的目录结构之前查看工具链的目录结构,以便找到该文件的目标平台特定版本。这在查找程序和库时尤其重要。即使对于查找文件,文件的内容也可能在平台之间发生变化(例如特定于平台的配置标头)。
For cross-compiling scenarios, the set of search locations becomes considerably more complex. Cross compiling toolchains are often collected under their own directory structure to keep them separate from the default host toolchain. When conducting searches for a particular file, it is generally desirable to first look in the toolchain’s directory structure ahead of those of the host so that a target platform-specific version of the file will be found. This is especially important when finding programs and libraries. Even for finding files, it may be the case that the content of files could change between platforms (e.g. a platform-specific configuration header).
为了支持交叉编译场景,整个搜索位置集可以重新定位到文件系统的不同部分。该
CMAKE_FIND_ROOT_PATH变量可以设置为附加目录的列表,在这些目录中重新设置搜索位置集的根(即将列表中的每个项目添加到每个搜索位置)。该CMAKE_SYSROOT变量还可以以类似的方式影响搜索根。此变量旨在指定充当交叉编译场景的系统根目录的单个目录,并且只能在工具链文件中设置它,而不能由项目本身设置。它也会影响编译期间使用的标志。从CMake 3.9开始,更专门的变量CMAKE_SYSROOT_COMPILE也
CMAKE_SYSROOT_LINK具有类似的效果。CMAKE_FIND_ROOT_PATH如果任何非 root 位置已位于、CMAKE_SYSROOT或指定的位置之一下
CMAKE_SYSROOT_COMPILE,
CMAKE_SYSROOT_LINK则不会重新对其进行 root。位于变量指定的路径下的非根路径CMAKE_STAGING_PREFIX也不会被重新根化。此外,所有命令的未记录行为find_…()
是不重新建立以字符开头的任何非根路径~(这是为了避免重新建立位于用户主目录下的目录)。
To support cross-compilation scenarios, the entire set of search locations can
be re-rooted to a different part of the file system. The
CMAKE_FIND_ROOT_PATH variable can be set to a list of additional
directories at which to re-root the set of search locations (i.e. prepend each
item in the list to every search location). The CMAKE_SYSROOT variable
can also affect the search root in a similar way. This variable is intended to
specify a single directory acting as the system root for a cross-compiling
scenario and it should only be set in a toolchain file, never by a project
itself. It affects flags used during compilation as well. From CMake 3.9, the
more specialized variables CMAKE_SYSROOT_COMPILE and
CMAKE_SYSROOT_LINK also have a similar effect. If any of the non-rooted
locations are already under one of the locations specified by
CMAKE_FIND_ROOT_PATH, CMAKE_SYSROOT, CMAKE_SYSROOT_COMPILE or
CMAKE_SYSROOT_LINK, it will not be re-rooted. A non-rooted path that sits
under a path specified by the variable CMAKE_STAGING_PREFIX will also not
be re-rooted. Furthermore, an undocumented behavior of all find_…()
commands is to not re-root any non-rooted path that starts with a ~ character
(this is intended to avoid re-rooting directories that sit under the user’s
home directory).
在重新扎根和非扎根位置之间搜索的默认顺序由变量控制CMAKE_FIND_ROOT_PATH_MODE_INCLUDE。CMAKE_FIND_ROOT_PATH_BOTH通过向命令提供、ONLY_CMAKE_FIND_ROOT_PATH或
NO_CMAKE_FIND_ROOT_PATH选项之一,也可以在每次调用的基础上覆盖它
find_file()。下表总结了此模式变量、关联选项和最终搜索顺序的影响:
The default order of searching among the re-rooted and non-rooted locations is
controlled by the CMAKE_FIND_ROOT_PATH_MODE_INCLUDE variable.
That can also be overridden on a per-call basis by providing one of the
CMAKE_FIND_ROOT_PATH_BOTH, ONLY_CMAKE_FIND_ROOT_PATH or
NO_CMAKE_FIND_ROOT_PATH options to the find_file() command.
The following table summarizes the effects of this mode variable, the
associated options and the final search order:
| 查找模式 | find_file()选项 |
搜索顺序 |
|---|---|---|
|
|
|
|
|
|
|
|
可能需要强制find_file()忽略可能包含已知不合适的匹配文件的特定路径。在交叉编译场景中,可能需要忽略一些特定的主机路径以确保找到特定于目标的文件而不是特定于主机的文件。项目可以将
CMAKE_IGNORE_PATH变量设置为要从搜索中排除的目录列表。这些路径不是递归的,因此它们不能用于排除目录结构的整个部分,它们需要显式指定每个目录。该CMAKE_SYSTEM_IGNORE_PATH变量执行相同的操作,但它旨在由工具链设置填充。无论是否交叉编译,这两个
…IGNORE_PATH变量都适用,但在不交叉编译时设置它们是不常见的。
It may be desirable to force find_file() to ignore particular paths that may
contain a matching file known to be unsuitable. In a cross-compiling scenario,
ignoring some specific host paths may be needed to ensure target-specific
rather than host-specific files are found. Projects may set the
CMAKE_IGNORE_PATH variable to a list of directories to exclude from the
search. These paths are not recursive, so they cannot be used to exclude a
whole section of a directory structure, they need to specify each directory
explicitly. The CMAKE_SYSTEM_IGNORE_PATH variable does the same thing,
but it is intended to be populated by the toolchain setup. Both of these
…IGNORE_PATH variables apply regardless of whether cross-compiling or not,
but it would be unusual for them to be set when not cross-compiling.
开发人员还应该意识到,find_file()只能提供一个位置,但某些交叉编译情况支持构建安排,可以在设备和模拟器构建之间切换,而无需重新运行 CMake。这意味着如果结果find_file()取决于使用两者中的哪一个,那么它们是不可靠的。这方面对于查找库更为重要,并在下面的第 24.4 节“查找库”中进行了更详细的讨论。
Developers should also be aware that find_file() can only provide one
location, but some cross compiling situations support build arrangements that
can switch between device and simulator builds without re-running CMake. This
means that if the results of find_file() depend on which of the two is being
used, they are unreliable. This aspect is even more important for finding
libraries and is discussed in more detail in Section 24.4, “Finding Libraries” further
below.
项目可能希望找到包含特定文件的目录而不是实际文件本身。该find_path()命令提供了此功能,并且在所有方面都相同find_file(),只是要查找的文件的目录存储在结果变量中。
A project may wish to find the directory containing a particular file rather
than the actual file itself. The find_path() command provides this
functionality and is identical to find_file() in every way except that the
directory of the file to be found is stored in the result variable.
查找程序与查找文件仅略有不同。该find_program()命令采用与 完全相同的一组参数
find_file(),以及另一个可选参数NAMES_PER_DIR。该find_program()命令还支持类似的缩写形式。find_program()下面描述了与 相比的差异
find_file(),虽然看起来很复杂,但在大多数情况下,它只是描述了人们在逻辑上可能期望的差异,但突出显示了一些例外:
Finding programs is only slightly different to finding files.
The find_program() command takes exactly the same set of arguments as
find_file(), as well as one more optional argument, NAMES_PER_DIR.
The find_program() command also supports a similar short form.
The following describes the differences for
find_program() compared to find_file(), and while it may seem complicated,
for the most part it just describes the differences one might logically expect
but with a few exceptions highlighted:
CMAKE_PREFIX_PATH,find_file()附加include
到每个项目。find_program()而是附加bin和sbin作为要检查的搜索位置。该CMAKE_LIBRARY_ARCHITECTURE变量对 没有影响find_program()。
CMAKE_PROGRAM_PATH替换CMAKE_INCLUDE_PATH但以完全相同的方式使用。CMAKE_PROGRAM_PATH仅由find_program().
CMAKE_APPBUNDLE_PATH替换CMAKE_FRAMEWORK_PATH但以完全相同的方式使用。它仅由find_program()和
使用find_package()。
CMAKE_PREFIX_PATH, find_file() appends include
to each item. find_program() instead appends bin and sbin as search
locations to be checked. The CMAKE_LIBRARY_ARCHITECTURE variable has no
effect for find_program().
CMAKE_PROGRAM_PATH replaces CMAKE_INCLUDE_PATH but is otherwise used
in exactly the same way. CMAKE_PROGRAM_PATH is used only by find_program().
CMAKE_APPBUNDLE_PATH replaces CMAKE_FRAMEWORK_PATH but is otherwise
used in exactly the same way. It is used only by find_program() and
find_package().
INCLUDE没有任何意义,find_program()
并且检查中的每一项PATH均不做任何修改。所有平台上的行为都是相同的。
INCLUDE has no meaning for find_program()
and each item in the PATH is checked without any modification. The behavior
is the same on all platforms.
NAMES通常,当使用该选项提供多个名称时,会在继续搜索列表中的下一个名称之前检查所有搜索位置是否有给定名称。该find_program()命令支持一个NAMES_PER_DIR
反转此顺序的选项,在转到下一个位置之前检查特定搜索位置的每个名称。该NAMES_PER_DIR选项是在 CMake 3.4 中添加的。
.com和.exe
也会自动检查,因此无需提供此类扩展名作为程序名称的一部分来查找。首先检查这些扩展名,然后再检查不带扩展名的名称。请注意,不会自动搜索.bat和文件。.cmd
find_file()用于CMAKE_FIND_FRAMEWORK确定框架和非框架路径之间的搜索顺序,则find_program()使用
CMAKE_FIND_APPBUNDLE。它在 Apple 平台的应用程序捆绑包和非捆绑包路径之间提供类似的控制。两个变量支持的值相同,并且它们对于捆绑包具有预期的等效含义。查找文件将在子目录中查找Headers,而查找程序将在Contents/MacOS子目录中查找并将结果设置为应用程序包中的可执行文件。
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE对 没有影响find_program(),它被替换为CMAKE_FIND_ROOT_PATH_MODE_PROGRAM具有同等效果但find_program()仅适用于的变量。交叉编译时,通常是寻找主机平台工具而不是目标平台上的程序,因此
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM经常设置为NEVER.
NAMES option is used to
provide multiple names. The find_program() command supports a NAMES_PER_DIR
option which reverses this order, checking each name for a particular search
location before moving on to the next location. The NAMES_PER_DIR option was
added in CMake 3.4.
.com and .exe
are automatically checked as well, so there is no need to provide such
extensions as part of the program name to find. These extensions are checked
first before names without the extensions. Note that .bat and .cmd files
will not be searched for automatically.
find_file() uses CMAKE_FIND_FRAMEWORK to determine the search
order between framework and non-framework paths, find_program() uses
CMAKE_FIND_APPBUNDLE.
It provides similar control between app bundle and non-bundle paths for Apple
platforms.
The supported values are the same for both variables and they have the expected
equivalent meaning for bundles.
Whereas finding files will look in a Headers subdirectory, finding programs
will look in the Contents/MacOS subdirectory and set the result to the
executable within the app bundle.
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE has no effect on find_program(), it is
replaced by the CMAKE_FIND_ROOT_PATH_MODE_PROGRAM variable which has the
equivalent effect but applies exclusively to find_program() only. When
cross-compiling, it is usually the case that it is a host platform tool being
sought rather than a program on the target platform, so
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM is frequently set to NEVER.
查找库也类似于查找文件。该命令支持与
附加选项find_library()相同的选项集。以下差异适用:find_file()NAMES_PER_DIR
Finding libraries is also similar to finding files.
The find_library() command supports the same set of options as find_file()
plus an additional NAMES_PER_DIR option.
The following differences apply:
CMAKE_PREFIX_PATH,find_file()附加include
到每个项目,而find_library()改为附加lib。该
CMAKE_LIBRARY_ARCHITECTURE变量也以与 相同的方式受到尊重
find_file()。
CMAKE_LIBRARY_PATH替换CMAKE_INCLUDE_PATH但以完全相同的方式使用。CMAKE_LIBRARY_PATH仅由find_library(). 该CMAKE_FRAMEWORK_PATH变量的使用方式与 完全相同
find_file()。
CMAKE_PREFIX_PATH, find_file() appends include
to each item, whereas find_library() instead appends lib. The
CMAKE_LIBRARY_ARCHITECTURE variable is also honored in the same way as for
find_file().
CMAKE_LIBRARY_PATH replaces CMAKE_INCLUDE_PATH but is otherwise used
in exactly the same way. CMAKE_LIBRARY_PATH is used only by find_library().
The CMAKE_FRAMEWORK_PATH variable is used in exactly the same way as for
find_file().
find_file(). 而是
参考环境变量INCLUDE。LIB此外,基于 的搜索位置遵循
PATH与 相同的复杂逻辑find_file(),只不过lib附加到每个前缀而不是include。正如 一样find_file(),复杂的PATH逻辑仅适用于 Windows。
find_file(). Instead of INCLUDE, the LIB
environment variable is consulted. Furthermore, the search locations based on
PATH follow the same complex logic as for find_file(), except that lib is
appended to each prefix rather than include. Just as for find_file(), the
complex PATH logic only applies on Windows.
NAMES_PER_DIR选项的含义与 的含义完全相同
find_program()。它仅适用于 CMake 3.4 或更高版本。
find_file()都find_library()用于CMAKE_FIND_FRAMEWORK确定框架和非框架路径之间的搜索顺序。在 的情况下find_library(),如果找到框架,则顶级.framework目录的名称将存储在结果变量中。
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE对 没有影响,它被具有等效效果但仅适用于 的变量find_library()替换。在 Apple 平台上,在设置 之前请仔细考虑
,因为库可能会构建为支持多个目标平台的胖二进制文件。这些胖二进制文件可能不驻留在目标平台特定的路径下,因此可能仍然需要搜索主机平台路径来查找它们。CMAKE_FIND_ROOT_PATH_MODE_LIBRARYfind_library()CMAKE_FIND_ROOT_PATH_MODE_LIBRARYONLY
NAMES_PER_DIR option has exactly the same meaning as it does for
find_program().
It is only available with CMake 3.4 or later.
find_file() and find_library() use CMAKE_FIND_FRAMEWORK to
determine the search order between framework and non-framework paths. In the
case of find_library(), if a framework is found then the name of the top
level .framework directory is stored in the result variable.
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE has no effect on find_library(), it is
replaced by the CMAKE_FIND_ROOT_PATH_MODE_LIBRARY variable which has the
equivalent effect but applies exclusively to find_library(). On Apple
platforms, consider carefully before setting
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY to ONLY, as libraries may be built as fat
binaries which support multiple target platforms. These fat binaries may not
reside under target platform-specific paths, so it may still be necessary to
search host platform paths to find them.
使用时存在进一步的行为差异find_library()。平台对库名称有不同的约定,例如
lib在大多数 Unix 平台上添加前缀。文件扩展名在不同平台上也有很大差异。Windows 上的 DLL 可以具有具有不同文件扩展名的关联导入库。
Further behavioral differences apply when using find_library().
Platforms have different conventions for library names, such as prepending
lib on most Unix platforms.
The file extensions also vary considerably across platforms.
DLLs on Windows can have an associated import library with a different file
extension.
该find_library()命令尽力抽象掉大部分差异,允许项目仅指定库的基本名称作为要搜索的名称。如果目录同时包含静态库和共享库,则将找到共享库。大多数时候,这种抽象工作得很好,但在某些情况下,覆盖这种行为可能很有用。一种常见的情况是优先考虑静态库而不是共享库,可能仅在某些平台上而不是其他平台上。以下简单的示例foobar在 Linux 上更喜欢静态库而不是共享库,但在 macOS 或 Windows 上则不然:
The find_library() command does its best to abstract away
most of these differences, allowing projects to specify just the base name of
the library as the name to search for. Where a directory contains both static
and shared libraries, the shared library will be the one found. Most of the
time, this abstraction works well, but in some circumstances it can be useful
to override this behavior. One common case is to give priority to static
libraries ahead of shared libraries, potentially only on some platforms and not
others. The following naive example would prefer a static foobar library
ahead of shared on Linux, but not on macOS or Windows:
# WARNING: Not robust!
find_library(FOOBAR_LIBRARY NAMES libfoobar.a foobar)# WARNING: Not robust!
find_library(FOOBAR_LIBRARY NAMES libfoobar.a foobar)
请记住,优先级覆盖仅适用于在特定目录中找到的库。如果搜索位置集合使得在包含静态库的目录之前搜索仅包含共享库的目录,则上述技术将不会导致找到静态库。确保静态库在所有搜索位置上优先于共享库的更可靠方法是使用多次调用,如下find_library()所示:
Keep in mind that the priority override only applies to libraries found within
a particular directory. If the set of search locations is such that a directory
containing just a shared library is searched before a directory that contains a
static library, then the above technique will not result in the static library
being found. The more robust way to ensure that a static library is given
priority over shared libraries across all search locations is to use multiple
calls to find_library() like so:
# Better, static library now has priority across
# all search locations
find_library(FOOBAR_LIBRARY libfoobar.a)
find_library(FOOBAR_LIBRARY foobar)# Better, static library now has priority across
# all search locations
find_library(FOOBAR_LIBRARY libfoobar.a)
find_library(FOOBAR_LIBRARY foobar)
请注意,此类技术不能在 Windows 上使用,因为静态库和共享库(即 DLL)的导入库具有相同的文件名,包括后缀(例如foobar.lib)。因此,不能通过文件名来区分两种类型的库。
Note that such techniques cannot be used on Windows because static libraries
and the import library for shared libraries (i.e. DLLs) have the same file
name, including suffix (e.g. foobar.lib). Therefore, the file name cannot be
used to differentiate between the two types of libraries.
库处理特有的另一个复杂之处是许多平台同时支持 32 位和 64 位体系结构。可能有 32 位和 64 位版本的库安装在不同的位置,但文件名相同。用于分隔此类多库系统上不同体系结构的目录结构可能会有所不同,即使在同一平台的发行版之间也是如此。例如,某些发行版将 64 位库放在lib目录下,将 32 位库放在lib32. 其他人将 64 位库放在 下lib64,将 32 位库放在
lib. 其他平台使用另一种变体,即libx32子目录。CMake 通常会意识到这些变化,并且在设置平台默认值时,它会填充全局属性FIND_LIBRARY_USE_LIB32_PATHS,
FIND_LIBRARY_USE_LIB64_PATHS并FIND_LIBRARY_USE_LIBX32_PATHS使用适当的值来控制应首先搜索哪些特定于体系结构的目录(如果有)。项目可以使用变量用自己的自定义前缀覆盖它们
CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX,但这种需要应该很少见。
Another complication unique to library handling is that many platforms support
both 32- and 64-bit architectures.
There may be both 32- and 64-bit versions of libraries installed to different
locations, but with the same file names.
The directory structure used to separate the different architectures on such
multilib systems can vary, even between distributions for the same platform.
For example, some distributions place 64-bit libraries under lib directories
and 32-bit libraries under lib32.
Others place 64-bit libraries under lib64 and the 32-bit libraries under
lib.
Other platforms use yet another variation, a libx32 subdirectory.
CMake is generally aware of the variations and when setting up the platform
defaults, it populates the global properties FIND_LIBRARY_USE_LIB32_PATHS,
FIND_LIBRARY_USE_LIB64_PATHS and FIND_LIBRARY_USE_LIBX32_PATHS with
appropriate values to control which architecture-specific directories should be
searched first, if any.
Projects can override these with their own custom prefix using the
CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX variable, but the need for this
should be very rare.
当特定于体系结构的后缀处于活动状态时(无论是来自上述全局属性之一还是来自变量CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX),用于使用特定于体系结构的位置来增强搜索位置的逻辑非常重要。搜索位置路径中以 结尾的任何目录都会lib使用特定于体系结构的等效目录进行扩充。这种情况在整个路径中递归发生,因此
在某些 64 位系统上,类似的搜索位置/opt/mylib/foo/lib可能会导致搜索位置集扩展到/opt/mylib64/foo/lib64、/opt/mylib64/foo/lib、/opt/mylib/foo/lib64
和。/opt/mylib/foo/lib即使搜索位置不以 结尾lib,它仍然会使用架构后缀位置进行扩充,因此搜索位置/opt/foo可能会导致/opt/foo64并
/opt/foo在某些 64 位系统上被搜索。
When an architecture-specific suffix is active (whether from one of the above
global properties or from the CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX variable),
the logic used to augment the search locations with architecture-specific
locations is non-trivial. Any directory anywhere in the search location path
that ends with lib is augmented with an architecture-specific equivalent.
This occurs recursively throughout the path, so a search location like
/opt/mylib/foo/lib may result in the set of search locations being expanded
out to /opt/mylib64/foo/lib64, /opt/mylib64/foo/lib, /opt/mylib/foo/lib64
and /opt/mylib/foo/lib on some 64-bit systems. Even if a search location does
not end with lib, it will still be augmented with an architecture-suffixed
location, so a search location /opt/foo may result in /opt/foo64 and
/opt/foo being searched on some 64-bit systems.
特定于体系结构的搜索路径增强的细节通常不是开发人员需要关心的事情。在发现不需要的库或丢失所需的库的情况下,使用变量强制结果可能CMAKE_LIBRARY_PATH比尝试操纵特定于体系结构的逻辑更直接。通常不需要详细了解所涉及的复杂性,对上述几点的简单认识通常就足够了,如果没有其他原因,只是为了减少 CMake 如何在特定于体系结构的位置找到库的一些神秘感。
The details of the architecture-specific search path augmentation are not
typically something developers need to concern themselves with. In those
situations where undesirable libraries are being found or desired libraries are
being missed, it may be more straightforward to coerce the result using
variables like CMAKE_LIBRARY_PATH rather than trying to manipulate the
architecture-specific logic. A detailed knowledge of the intricacies involved
is not typically needed, a simple awareness of the above points should
generally be sufficient, if for no other reason than to reduce some of the
mystery around how CMake finds libraries in architecture-specific locations.
使用支持在构建时在设备和模拟器配置之间切换的 CMake 生成器时需要特别小心。对于这种情况,任何find_library()结果通常都是无法使用的,因为他们只能找到设备或模拟器的库,而不能同时找到两者的库。即使重新运行 CMake,它也会保留其缓存结果,因此不会更新库位置,除非首先手动删除相关缓存条目。这是 Xcode 构建中特别常见的问题,项目可能希望使用它find_library()来定位各种框架或通用库(例如 zlib)。对于这些情况,项目别无选择,只能直接指定链接器标志而不使用路径,让链接器在其搜索路径上查找库。对于 Apple 框架,这意味着指定两个值,因为框架是使用-framework <FrameworkName>. 对于像 zlib 这样的普通库,更传统的-lz就足够了。
Special care needs to be exercised when working with a CMake generator that
supports switching between device and simulator configurations at build time.
Any find_library() results would generally be unusable for such cases,
since they could only ever find a library for either the device or the
simulator, but not both. Even if CMake is re-run, it would retain
its cached results and so would not update the library location unless the
relevant cache entry was manually deleted first. This is a particularly common
problem with Xcode builds where projects might want to use find_library() to
locate various frameworks or common libraries such as zlib. For these
situations, projects have little choice but to specify the linker flags
directly without paths instead, leaving the linker to find the library on its
search path. For Apple frameworks, this means specifying two values
since frameworks are added using -framework <FrameworkName>. For ordinary
libraries like zlib, the more traditional -lz would be sufficient.
前面几节中讨论的各种find_…()命令都集中于查找一个特定项目。然而,通常这些项目只是较大包的一部分,并且整个包可能具有项目可能感兴趣的自己的特征,例如版本号或对某些功能的支持。项目通常希望将包作为一个单元来查找,而不是手动将其不同部分拼凑在一起。
The various find_…() commands discussed in the preceding sections all focus
on finding one specific item. Quite often, however, these items are just one
part of a larger package and the package as a whole may have its own
characteristics that projects could be interested in, such as a version number
or support for certain features. Projects will generally want to find the
package as a single unit rather than piece together its different parts
manually.
在 CMake 中定义包的主要方式有两种:作为模块或通过配置详细信息。find_…()配置详细信息通常作为包本身的一部分提供,并且它们与前面部分中讨论的各种命令的功能更紧密地一致。另一方面,模块通常是由与包无关的东西定义的(通常由 CMake 或项目本身),因此,随着包随着时间的推移而发展,它们很难保持最新。
There are two main ways packages are defined in CMake, either as a module or
through config details. Config details are usually provided as part of the
package itself and they are more closely aligned with the functionality of the
various find_…() commands discussed in the preceding sections. Modules, on
the other hand, are typically defined by something unrelated to the package
(usually by CMake or by projects themselves) and as a result, they are harder
to keep up to date as the package evolves over time.
模块和配置文件通常定义包的变量和导入的目标。这些可以提供程序、库、消费目标使用的标志等的位置。还可以定义函数和宏。对于所提供的内容没有任何要求,但 CMake 开发人员手册中规定了一些约定。项目作者必须查阅每个模块或包的文档以了解所提供的内容。作为一般指南,较旧的模块倾向于提供遵循相当一致模式的变量,而较新的模块和配置实现通常定义导入的目标。在同时提供变量和导入目标的情况下,项目应该更喜欢后者,因为后者具有卓越的稳健性以及与 CMake 的传递依赖功能的更好集成。
Module and config files typically define variables and imported targets for the package. These may provide the location of programs, libraries, flags to be used by consuming targets and so on. Functions and macros can also be defined. There is no set of requirements for what will be provided, but there are some conventions which are stated in the CMake developer manual. Project authors must consult the documentation of each module or package to understand what is provided. As a general guide, older modules tend to provide variables that follow a fairly consistent pattern, whereas newer modules and config implementations usually define imported targets. Where both variables and imported targets are provided, projects should prefer the latter due to their superior robustness and better integration with CMake’s transitive dependency features.
项目通常使用该命令查找包find_package(),该命令有短格式和长格式。通常应该首选短格式,因为它更简单并且支持模块和配置包,而长格式不支持模块。然而,长形式确实提供了对搜索的更多控制,使其在某些情况下更可取。
Projects normally look for a package using the find_package() command, which
has a short form and a long form. The short form should generally be preferred
because of its greater simplicity and because it supports both module and
config packages, whereas the long form does not support modules. The long form
does, however, provide more control over the search, making it preferable in
certain situations.
简短的形式只有几个选项,可以总结如下:
The short form has only a few options and can be summarized as follows:
find_package(packageName
[version [EXACT] ]
[QUIET] [REQUIRED]
[ [COMPONENTS] component1 [component2...] ]
[OPTIONAL_COMPONENTS component3 [component4...] ]
[MODULE]
[NO_POLICY_SCOPE]
)find_package(packageName
[version [EXACT] ]
[QUIET] [REQUIRED]
[ [COMPONENTS] component1 [component2...] ]
[OPTIONAL_COMPONENTS component3 [component4...] ]
[MODULE]
[NO_POLICY_SCOPE]
)
可选version参数指示包必须是指定版本或更高版本,或者如果EXACT也给出则完全匹配。当使用 CMake 3.19 或更高版本时,也version可以指定为版本范围。versionMin...versionMax版本范围以或 的
形式表示versionMin...<versionMax。该versionMin范围的一部分被视为单个版本号。这是软件包所需的最低版本。对于版本约束的上限,第一种形式要求包版本不大于versionMax,而第二种形式要求它严格小于versionMax。EXACT如果使用版本范围,则不能给出关键字。请注意,软件包可能不知道版本范围。他们的模块或配置文件可能有一个相当旧的实现,早于 CMake 的版本范围支持。在这些情况下,包通常会忽略versionMax部分需求。
第 26.8.1 节“CMake 项目的配置文件”讨论了包如何处理版本范围的进一步含义。
The optional version argument indicates that the package must be of the
specified version or higher, or match exactly if EXACT is also given.
When using CMake 3.19 or later, the version can alternatively be specified
as a version range instead.
Version ranges are expressed in the form versionMin...versionMax or
versionMin...<versionMax.
The versionMin part of the range is treated just like a single version
number.
It is the minimum required version of the package.
For the upper end of the version constraint, the first form requires the
package version to be no greater than versionMax, whereas the second form
requires it to be strictly less than versionMax.
The EXACT keyword cannot be given if a version range is used.
Be aware that packages may not know about version ranges.
Their module or config file may have a fairly old implementation that
pre-dates CMake’s version range support.
In these cases, the packages will typically ignore the versionMax part of
the requirement.
Section 26.8.1, “Config Files For CMake Projects” discusses further implications for how
packages may handle version ranges.
包可能是可选的,这意味着项目可以使用它(如果可用),或者如果找不到合适的包,则可以在没有它的情况下工作。如果包是强制性的,REQUIRED则应提供该选项,以便在找不到适当的包时 CMake 将停止并出现错误。与其他find_…()命令不同,所有 CMake 版本都
REQUIRED支持find_package().
A package may be optional, meaning the project can use it if available or work
without it if an appropriate package cannot be found.
If a package is mandatory, the REQUIRED option should be provided so that
CMake will halt with an error if an appropriate package could not be found.
Unlike the other find_…() commands, all CMake versions support the
REQUIRED option for find_package().
通常,find_package()将记录失败消息,但该QUIET
选项可用于抑制可选包的失败消息(无法抑制强制包的失败消息)。
QUIET还将抑制通常在第一次找到包时打印的消息。典型用途QUIET是防止丢失可选包的消息,以便开发人员不太可能认为这是一个错误。
Normally, find_package() will log messages for failures, but the QUIET
option can be used to suppress them for optional packages (failure messages for
a mandatory package cannot be suppressed).
QUIET will also suppress messages that would normally be printed the first
time a package is found.
A typical use for QUIET is to prevent messages for a missing optional package
so that developers are less likely to think it is an error.
与组件相关的选项允许项目指示他们对包的哪些部分感兴趣。并非所有包都支持组件,是否定义组件以及组件代表什么取决于模块或配置实现。组件可能有用的一个示例是像 Qt 这样的大型软件包,其中可能不会安装所有组件。对于一个项目来说,仅仅说它想要 Qt 可能还不够,它可能还需要说 Qt 的哪些部分。该find_package()命令允许项目将组件指定为带有COMPONENTS参数的强制组件或带有OPTIONAL_COMPONENTS参数的可选组件。例如,以下调用需要 Qt 5.9 或更高版本,该Gui组件必须可用并且DBus
是可选的:
The component-related options allow a project to indicate what parts of the
package they are interested in. Not all packages support components, it is up
to the module or config implementation whether or not components are defined
and what the components represent. An example where components may be useful is
a large package like Qt where not all components might be installed. It
may not be enough for a project to just say it wants Qt, it may also need to
say which parts of Qt. The find_package() command allows the project to
specify components as mandatory with the COMPONENTS arguments or as optional
with the OPTIONAL_COMPONENTS arguments. For example, the following call
requires Qt 5.9 or later, the Gui component must be available and DBus
is optional:
find_package(Qt5 5.9 REQUIRED
COMPONENTS Gui
OPTIONAL_COMPONENTS DBus
)find_package(Qt5 5.9 REQUIRED
COMPONENTS Gui
OPTIONAL_COMPONENTS DBus
)
REQUIRED当存在该选项时,COMPONENTS可以省略关键字,并将强制组件放在 后面REQUIRED。当没有可选组件时,这种情况很常见:
When the REQUIRED option is present, the COMPONENTS keyword can be omitted
and the mandatory components placed after REQUIRED.
This is common when there are no optional components:
find_package(Qt5 5.9 REQUIRED Gui Widgets Network)find_package(Qt5 5.9 REQUIRED Gui Widgets Network)
如果一个包定义了组件但没有给 任何组件
find_package(),则由模块或配置定义如何处理。对于某些包,它可能被视为列出了所有组件,而对于其他包,它可能被解释为不需要任何组件(但仍可以定义包的基本细节,例如基础库、包版本等)。另一种可能性是缺少组件可能被视为错误。鉴于行为的变化,开发人员应该查阅他们希望找到的包的文档。
If a package defines components but no components are given to
find_package(), it is up to the module or config definition how this is
handled. For some packages, it may be treated as though all components were
listed, for others it may be interpreted as no components are required (basic
details of the package may still be defined though, such as base libraries,
package version, etc.). Another possibility is that the lack of components
could be treated as an error. Given the variation in behavior, developers
should consult the documentation for the package they wish to find.
缩写形式的其余选项不太常用。该
NO_POLICY_SCOPE关键字是 CMake 2.6 时代的历史遗留问题,项目应避免使用它。该MODULE关键字将调用限制为仅搜索模块而不是配置包。项目通常应避免使用此选项,因为他们不必关心如何定义包的实现细节,而只需说明包的要求。当MODULE不存在时,find_package()命令的缩写形式将首先搜索匹配的模块,然后如果没有找到这样的模块,它将搜索配置包。CMake 3.15 添加了对该CMAKE_FIND_PACKAGE_PREFER_CONFIG
变量的支持,可以将其设置为 true 以反转搜索首选项(默认情况下未设置以保留 3.15 之前的行为)。
The remaining options of the short form are less frequently used. The
NO_POLICY_SCOPE keyword is a historical hangover from the CMake 2.6 era and
projects should avoid using it. The MODULE keyword restricts the call to
searching only for modules and not config packages. Projects should generally
avoid using this option since they should not have to concern themselves with
the implementation details of how a package is defined, only with stating the
requirements on the package. When MODULE is not present, the short form of
the find_package() command will first search for a matching module, then if
no such module is found it will search instead for a config package.
CMake 3.15 added support for the CMAKE_FIND_PACKAGE_PREFER_CONFIG
variable, which can be set to true to reverse the search preference (it is
unset by default to preserve the pre-3.15 behavior).
模块最初是在第 11 章“模块”中讨论的。虽然使用该命令将非包模块合并到项目中include(),但包模块具有以下形式的文件名Find<packageName>.cmake,并且旨在通过调用来处理find_package()。因此,它们通常被称为“查找模块”。两者include()都将变量find_package()
视为CMAKE_MODULE_PATHCMake 应在作为每个 CMake 版本的一部分提供的模块集之前搜索的目录列表。
Modules were first discussed back in Chapter 11, Modules. While non-package modules
are incorporated into a project using the include() command, package modules
have a file name of the form Find<packageName>.cmake and are intended to be
processed by a call to find_package() instead. For this reason, they are
commonly referred to as Find modules. Both include() and find_package()
respect the CMAKE_MODULE_PATH variable as a list of directories that
CMake should search in before the set of modules that come as part of every
CMake release.
查找模块负责实现调用的所有方面
find_package(),包括定位包、执行版本检查、满足组件要求以及根据需要记录或不记录消息。并非所有查找模块都履行这些职责,它们可能会选择忽略包名称之外提供的部分或全部信息,因此请一如既往地查阅模块文档以确认预期行为。
Find modules are responsible for implementing all aspects of the
find_package() call, including locating the package, performing version
checks, fulfilling component requirements and logging or not logging messages
as appropriate. Not all find modules honor these responsibilities and they may
choose to ignore some or all of the information provided beyond the package
name, so as always, consult the module documentation to confirm the expected
behavior.
查找模块通常通过调用各种
find_…()命令来实现。因此,它们有时会受到与这些命令相关的缓存和环境变量的影响。该CMAKE_PREFIX_PATH
变量对于影响查找模块特别方便,因为指定的每个路径都充当基点,每个find_…()命令都在该基点之下附加其自己的特定于命令的子目录。对于遵循合理标准布局的包,仅添加包的基本安装位置CMAKE_PREFIX_PATH通常足以让查找模块找到它需要的所有包组件。
Find modules are usually implemented in terms of calls to the various
find_…() commands. As a result, they can sometimes be affected by the cache
and environment variables relevant to those commands. The CMAKE_PREFIX_PATH
variable is especially convenient for influencing find modules because each
path specified acts as a base point below which each find_…() command
appends its own command-specific subdirectories. For packages that follow a
reasonably standard layout, adding just the base install location of the
package to CMAKE_PREFIX_PATH is often enough for the find module to find all
the package components it needs.
与查找模块相比,具有配置详细信息的包为项目提供了更丰富、更健壮的方式来检索有关该包的信息。配置模式下提供了更广泛的find_package()选项集,该命令的完整长形式与其他
find_…()命令有许多相似之处:
Compared to find modules, packages with config details offer a much richer,
more robust way for projects to retrieve information about that package. A much
more extensive set of find_package() options are available in config mode,
with the full long form of the command having many similarities to the other
find_…() commands:
find_package(packageName
[version [EXACT] ]
[QUIET | REQUIRED]
[ [COMPONENTS] component1 [component2...] ]
[OPTIONAL_COMPONENTS component3 [component4...] ]
[NO_MODULE | CONFIG]
[NO_POLICY_SCOPE]
[NAMES name1 [name2 ...] ]
[CONFIGS fileName1 [fileName2...] ]
[HINTS path1 [path2 ... ] ]
[PATHS path1 [path2 ... ] ]
[PATH_SUFFIXES suffix1 [suffix2 ...] ]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
[<skip-options>] # See further below
)find_package(packageName
[version [EXACT] ]
[QUIET | REQUIRED]
[ [COMPONENTS] component1 [component2...] ]
[OPTIONAL_COMPONENTS component3 [component4...] ]
[NO_MODULE | CONFIG]
[NO_POLICY_SCOPE]
[NAMES name1 [name2 ...] ]
[CONFIGS fileName1 [fileName2...] ]
[HINTS path1 [path2 ... ] ]
[PATHS path1 [path2 ... ] ]
[PATH_SUFFIXES suffix1 [suffix2 ...] ]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
[<skip-options>] # See further below
)
当find_package()使用仅长格式支持的选项调用时,将跳过对 Find 模块的搜索。orNO_MODULE关键字CONFIG允许将与短格式匹配的调用视为长格式,因此仅搜索配置详细信息(两个关键字是等效的)。
When find_package() is called with an option only supported by the long form,
the search for a Find module is skipped. The NO_MODULE or CONFIG keywords
allow a call that would otherwise match the short form to be treated as the
long form and hence only search for config details (both keywords are
equivalent).
搜索配置详细信息时,将查找名为或默认的find_package()文件
。该选项可用于指定要搜索的一组不同的文件名,但不鼓励这样做。非默认文件名要求每个想要查找该包的项目都知道非默认文件名。<packageName>Config.cmake<lowercasePackageName>-config.cmakeCONFIGS
When searching for config details, find_package() looks for a file named
<packageName>Config.cmake or <lowercasePackageName>-config.cmake by default.
The CONFIGS option can be used to specify a different set of file names to
search for instead, but this is discouraged.
Non-default file names require every project wanting to find that package to be
aware of the non-default file name.
当找到配置文件时,find_package()还会在同一目录中查找关联的版本文件。版本文件具有Versionor
-version附加到基本名称,因此FooConfig.cmake将导致查找名为FooConfigVersion.cmake或 的
版本文件FooConfig-version.cmake,而foo-config.cmake将导致查找
foo-configVersion.cmake或foo-config-version.cmake。包不需要提供版本文件,但它们通常会提供。如果版本详细信息包含在调用中,find_package()但没有该包的版本文件,则版本要求被视为失败。
When a config file is found, find_package() also looks for an associated
version file in the same directory. The version file has Version or
-version appended to the base name, so FooConfig.cmake would result in
looking for a version file named FooConfigVersion.cmake or
FooConfig-version.cmake, while foo-config.cmake would result in looking for
foo-configVersion.cmake or foo-config-version.cmake. Packages are not
required to provide a version file, but they usually do. If version details are
included in a call to find_package() but there is no version file for that
package, the version requirements are deemed to have failed.
搜索的位置遵循与其他find_…()
命令类似的模式,但也支持包注册表。然后,每个搜索位置都被视为可能的包安装基点,可以在该基点下搜索各种子目录:
The locations searched follow a similar pattern to the other find_…()
commands, except package registries are also supported. Each search location is
then treated as a possible package install base point below which a variety of
subdirectories may be searched:
<前缀>/ <前缀>/(cmake|CMake)/ <前缀>/<包名>*/ <前缀>/<包名>*/(cmake|CMake)/ <前缀>/(lib/<arch>|lib*|share)/cmake/<packageName>*/ <前缀>/(lib/<架构>|lib*|共享)/<包名>*/ <前缀>/(lib/<arch>|lib*|share)/<packageName>*/(cmake|CMake)/ <前缀>/<包名>*/(lib/<arch>|lib*|share)/cmake/<包名>*/ <前缀>/<包名>*/(lib/<架构>|lib*|共享)/<包名>*/ <前缀>/<包名>*/(lib/<arch>|lib*|share)/<包名>*/(cmake|CMake)/
<prefix>/ <prefix>/(cmake|CMake)/ <prefix>/<packageName>*/ <prefix>/<packageName>*/(cmake|CMake)/ <prefix>/(lib/<arch>|lib*|share)/cmake/<packageName>*/ <prefix>/(lib/<arch>|lib*|share)/<packageName>*/ <prefix>/(lib/<arch>|lib*|share)/<packageName>*/(cmake|CMake)/ <prefix>/<packageName>*/(lib/<arch>|lib*|share)/cmake/<packageName>*/ <prefix>/<packageName>*/(lib/<arch>|lib*|share)/<packageName>*/ <prefix>/<packageName>*/(lib/<arch>|lib*|share)/<packageName>*/(cmake|CMake)/
Apple 平台上还检查了以下内容:
The following are also checked on Apple platforms:
<前缀>/<包名称>.framework/Resources/ <前缀>/<包名称>.framework/Resources/CMake/ <前缀>/<包名称>.framework/Versions/*/Resources/ <前缀>/<包名称>.framework/Versions/*/Resources/CMake/ <前缀>/<包名称>.app/Contents/Resources/ <前缀>/<包名称>.app/Contents/Resources/CMake/
<prefix>/<packageName>.framework/Resources/ <prefix>/<packageName>.framework/Resources/CMake/ <prefix>/<packageName>.framework/Versions/*/Resources/ <prefix>/<packageName>.framework/Versions/*/Resources/CMake/ <prefix>/<packageName>.app/Contents/Resources/ <prefix>/<packageName>.app/Contents/Resources/CMake/
在上面,<packageName>不区分大小写,并且
仅在设置lib/<arch>时才搜索子目录
。CMAKE_LIBRARY_ARCHITECTURE子目录lib*代表一组目录,可能包括lib64、lib32和libx32,lib始终检查最后一个。如果该NAMES选项指定为
find_package(),则将检查上述所有目录中提供的每个名称。
In the above, <packageName> is treated case-insensitively and the
lib/<arch> subdirectories are only searched if
CMAKE_LIBRARY_ARCHITECTURE is set. The lib* subdirectories represent a
set of directories that may include lib64, lib32, libx32 and lib, the
last of which is always checked. If the NAMES option is given to
find_package(), all of the above directories are checked for each name
provided.
检查的搜索位置基点集遵循下表中定义的顺序,它与其他find_…()
命令有许多相似之处。大多数搜索位置可以通过添加关联的关键字来禁用
NO_…:
The set of search location base points checked follow the order defined in the
following table, which shares many similarities to the other find_…()
commands. Most search locations can be disabled by adding the associated
NO_… keyword:
| 地点 | 跳过选项 |
|---|---|
包根变量 Package root variables |
|
缓存变量(特定于 CMake) Cache variables (CMake-specific) |
|
环境变量(特定于 CMake) Environment variables (CMake-specific) |
|
通过 Paths specified via the |
|
环境变量(系统特定) Environment variables (system-specific) |
|
用户包注册表 User package registry |
|
缓存变量(特定于平台) Cache variables (platform-specific) |
|
系统包注册表 System package registry |
|
通过 Paths specified via the |
find_…()命令,在 CMake 3.9.0 中添加了对包根变量的支持作为搜索位置,由于向后兼容性问题在 3.9.1 中删除,并在 CMake 3.12 中再次重新添加。每次
find_package()调用时,它都会将<packageName>_ROOTCMake 和环境变量推送到内部维护的路径堆栈上。这些路径的使用方式与 完全相同CMAKE_PREFIX_PATH,不仅适用于当前对 的调用,还适用于可能作为处理的一部分调用的find_package()所有命令。实际上,这意味着如果
调用加载 Find 模块,则Find 模块内部调用的任何命令都将使用堆栈中的每个路径,就好像它是
检查任何其他路径之前的第一个路径一样。find_..()find_package()find_package()find_…()CMAKE_PREFIX_PATH
例如,假设find_package(Foo)调用导致FindFoo.cmake被加载。其中的任何find_…()命令都FindFoo.cmake将首先搜索
${Foo_ROOT}和$ENV{Foo_ROOT}(如果已设置),然后再继续检查其他搜索位置。如果包含FindFoo.cmake这样的调用
find_package(Bar)导致FindBar.cmake被加载,那么堆栈将包含${Bar_ROOT}、$ENV{Bar_ROOT}和${Foo_ROOT}。
$ENV{Foo_ROOT}此功能意味着嵌套的查找模块将首先搜索其每个父查找模块的前缀位置,因此不必通过CMAKE_PREFIX_PATH或其他类似的方法手动向下传播信息。在大多数情况下,项目可以忽略此功能,因为它应该透明地工作,而无需项目执行任何特定操作。它主要应该被认为是一种自动的便利。
find_…() commands, support for package root variables was
added as a search location in CMake 3.9.0, removed in 3.9.1 due to backward
compatibility issues and re-added again in CMake 3.12. Each time
find_package() is called, it pushes <packageName>_ROOT CMake and
environment variables onto an internally maintained stack of paths. These paths
are used in exactly the same way as CMAKE_PREFIX_PATH, not just for the
current call to find_package(), but all find_..() commands that might be
called as part of the find_package() processing. In practice, this means if a
find_package() call loads a Find module, then any find_…() commands the
Find module calls internally will use each path in the stack as though it was a
CMAKE_PREFIX_PATH first before checking any other paths.
For example, say a find_package(Foo) call resulted in FindFoo.cmake being
loaded. Any find_…() command within FindFoo.cmake would first search
${Foo_ROOT} and $ENV{Foo_ROOT} (if they were set) before moving on to check
other search locations. If FindFoo.cmake contained a call like
find_package(Bar) that resulted in FindBar.cmake being loaded, then the
stack would contain ${Bar_ROOT}, $ENV{Bar_ROOT}, ${Foo_ROOT} and
$ENV{Foo_ROOT}. This feature means nested Find modules will search the prefix
locations of each of their parent Find modules first, so that information
doesn’t have to be manually propagated down via CMAKE_PREFIX_PATH or another
similar method. For the most part, projects can ignore this functionality,
since it should work transparently without any specific action by the project.
It should mostly just be thought of as an automatic convenience.
CMAKE_PREFIX_PATH、CMAKE_FRAMEWORK_PATH和
CMAKE_APPBUNDLE_PATH。这些命令的工作方式与其他find_…()命令相同,只是CMAKE_PREFIX_PATH条目已经对应于包安装基点,因此不会
附加bin、lib、include等目录。
CMAKE_PREFIX_PATH, CMAKE_FRAMEWORK_PATH and
CMAKE_APPBUNDLE_PATH. These work the same way as for
the other find_…() commands except that CMAKE_PREFIX_PATH entries
already correspond to package install base points, so no directories like
bin, lib, include, etc. are appended.
find_…()。环境变量CMAKE_PREFIX_PATH,
CMAKE_INCLUDE_PATH和CMAKE_FRAMEWORK_PATH都使用特定于平台的路径分隔符(Unix 平台上为冒号,Windows 上为分号)。<packageName>_DIR在其他三个环境变量之前还会检查一个附加环境变量。
find_…() commands. The environment variables CMAKE_PREFIX_PATH,
CMAKE_INCLUDE_PATH and CMAKE_FRAMEWORK_PATH all use the platform-specific
path separator (colons on Unix platforms, semi-colons on Windows). An
additional environment variable <packageName>_DIR is also checked before the
other three.
PATH。每个条目都用作软件包安装基点,除了任何尾随bin或被sbin删除的条目。/usr这是在大多数系统上可能搜索默认系统位置的点。
PATH. Each entry
is used as a package install base point, except any trailing bin or sbin is
removed. This is the point at which default system locations like /usr are
likely to be searched on most systems.
find_…()命令相同的模式,提供…_SYSTEM_…特定于 CMake 的缓存变量的等效项。这些系统变量的名称是
CMAKE_SYSTEM_PREFIX_PATH、 ,CMAKE_SYSTEM_FRAMEWORK_PATH并且
CMAKE_SYSTEM_APPBUNDLE_PATH它们不打算由项目设置。
find_…() commands, providing …_SYSTEM_… equivalents of the
CMake-specific cache variables. The names of these system variables are
CMAKE_SYSTEM_PREFIX_PATH, CMAKE_SYSTEM_FRAMEWORK_PATH and
CMAKE_SYSTEM_APPBUNDLE_PATH and they are not intended to be set by the
project.
HINTS和PATHS
HINTS and PATHS
find_…()只是它们不支持表单的项目ENV someVar。
find_…() commands except they
do not support items of the form ENV someVar.
find_package(),用户和系统包注册表旨在提供一种使包易于查找的方法,而无需将它们安装在标准系统位置。有关更详细的讨论,请参阅下面第 24.5.1 节“包注册表” 。
find_package(), the user and system package registries are intended
to provide a way to make packages easily findable without having them installed
in standard system locations. See Section 24.5.1, “Package Registries” further below for a
more detailed discussion.
各种NO_…选项的工作方式与其他命令相同find_…()
,允许单独绕过每组搜索位置。该NO_DEFAULT_PATH关键字会导致除HINTSand之外的所有内容PATHS被绕过。在 CMake 3.16 或更高版本中,各种CMAKE_FIND_USE_…变量也具有与其他find_…()命令相同的效果。这些变量允许单独控制每个搜索位置的默认行为。该PATH_SUFFIXES选项也具有预期的效果,接受更多子目录来检查每个搜索位置下方。
The various NO_… options work the same way as for the other find_…()
commands, allowing each group of search locations to be bypassed individually.
The NO_DEFAULT_PATH keyword causes all but the HINTS and PATHS to
be bypassed.
With CMake 3.16 or later, the various CMAKE_FIND_USE_… variables also have
the same effects as for the other find_…() commands.
These variables allow the default behavior of each search location to be
controlled individually.
The PATH_SUFFIXES option has the expected effect too, accepting
further subdirectories to check below each search location.
该find_package()命令还支持与其他命令相同的搜索重新定位逻辑find_…()。CMAKE_SYSROOT、CMAKE_STAGING_PREFIX和
CMAKE_FIND_ROOT_PATH的考虑方式与其他命令相同,并且CMAKE_FIND_ROOT_PATH_BOTH、ONLY_CMAKE_FIND_ROOT_PATH
和NO_CMAKE_FIND_ROOT_PATH选项的含义也相同。当这三个选项均未提供时,默认的重新根模式由
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE具有可预测的有效值集(ONLY、NEVER或BOTH)的变量控制。
The find_package() command also supports the same search re-rooting logic as
the other find_…() commands. CMAKE_SYSROOT, CMAKE_STAGING_PREFIX and
CMAKE_FIND_ROOT_PATH are all considered in the same way as the other commands
and the meanings of the CMAKE_FIND_ROOT_PATH_BOTH, ONLY_CMAKE_FIND_ROOT_PATH
and NO_CMAKE_FIND_ROOT_PATH options are also equivalent. The default re-root
mode when none of these three options is provided is controlled by the
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE variable which has the predictable set
of valid values (ONLY, NEVER or BOTH).
与其他find_…()命令不同,在查找配置文件时,
find_package()不一定会在找到的第一个与条件匹配的包处停止搜索。搜索的某些部分考虑一系列搜索位置,并且搜索结果可能会返回搜索的该特定子分支的多个匹配项。通常,如果某个公共目录下安装了软件包的多个版本,并且每个版本在该公共点下都有一个版本化子目录,则可能会发生这种情况。在这种情况下,
将参考CMAKE_FIND_PACKAGE_SORT_ORDER和CMAKE_FIND_PACKAGE_SORT_DIRECTION
变量来根据候选版本详细信息对候选进行排序。
CMAKE_FIND_PACKAGE_SORT_DIRECTION必须具有值DEC或ASC来分别指示降序(选择最新)或升序(选择最旧)排序方向,同时CMAKE_FIND_PACKAGE_SORT_ORDER控制排序类型并记录了NAME,NATURAL或 的值NONE。如果设置为
NONE或根本未设置,则不执行排序,并且将使用找到的第一个有效包。该NAME设置按字典顺序排序,同时NATURAL
通过比较数字序列作为整数进行排序。下表演示了按降序排序时最后两种方法之间的区别,如果CMAKE_FIND_PACKAGE_SORT_DIRECTION未设置,这是默认行为:
Unlike the other find_…() commands, when looking for a config file,
find_package() does not necessarily stop searching at the first package it
finds that matches the criteria. Parts of the search consider a family of
search locations and the search results may return multiple matches for that
particular sub-branch of the search. Typically this might occur if there are
multiple versions of the package installed under some common directory, each
of which has a versioned subdirectory below that common point. In such cases, the
CMAKE_FIND_PACKAGE_SORT_ORDER and CMAKE_FIND_PACKAGE_SORT_DIRECTION
variables are consulted to sort the candidates based on their version details.
CMAKE_FIND_PACKAGE_SORT_DIRECTION must have the value DEC or ASC to
indicate a descending (choose the newest) or ascending (choose the oldest) sort
direction respectively, while CMAKE_FIND_PACKAGE_SORT_ORDER controls the type
of sorting and has documented values of NAME, NATURAL or NONE. If set to
NONE or not set at all, no sorting is performed and the first valid package
found will be used. The NAME setting sorts lexicographically, while NATURAL
sorts by comparing sequences of digits as whole numbers. The following table
demonstrates the difference between the last two methods when sorting in descending
order, which is the default behavior if CMAKE_FIND_PACKAGE_SORT_DIRECTION is
not set:
| 姓名 | 自然的 |
|---|---|
1.9 1.9 |
1.10 1.10 |
1.10 1.10 |
1.9 1.9 |
1.0 1.0 |
1.0 1.0 |
在实践中,搜索逻辑的复杂性通常远远超出了有效使用命令所需的详细程度find_package()。只要包遵循更常见的目录布局之一并位于较高级别的基本安装位置之一下,该find_package()命令通常会找到其配置文件,而无需进一步帮助。
In practice, the intricacies of the search logic are usually well beyond the
level of detail needed to use the find_package() command effectively. As long
as a package follows one of the more common directory layouts and sits under
one of the higher level base install locations, the find_package() command
will usually find its config file without further help.
一旦找到适合包的配置文件,
<packageName>_DIR缓存变量将被设置为包含该文件的目录。随后的调用find_package()将首先在该目录中查找,如果配置文件仍然存在,则使用它而无需进一步搜索。<packageName>_DIR如果该位置不再有包的配置文件,则将被忽略。这种安排可确保对find_package()同一包的后续调用速度更快,甚至从一次 CMake 调用到下一次调用也是如此,但如果删除包,仍然会执行搜索。但请注意,包位置的缓存也可能意味着 CMake 可能没有机会了解更佳位置中新添加的包。例如,操作系统可能预装了相当旧版本的软件包。第一次在项目上运行 CMake 时,它会找到旧版本并将其位置存储在缓存中。用户看到正在使用旧版本,并决定在其他目录下安装较新版本的包,将该位置添加到CMAKE_PREFIX_PATH并重新运行 CMake。在这种情况下,仍将使用旧版本,因为缓存仍指向旧包的位置。<packageName>_DIR在考虑新版本的位置之前,需要删除缓存条目或卸载旧版本。
Once a suitable config file for a package has been found, the
<packageName>_DIR cache variable will be set to the directory containing that
file. Subsequent calls to find_package() will then look in that directory
first and if the config file still exists, it is used without further
searching. <packageName>_DIR is ignored if there is no longer a config file
for the package at that location. This arrangement ensures that subsequent
calls to find_package() for the same package are much faster, even from one
invocation of CMake to the next, but the search is still performed if the
package is removed. Be aware, however, that the caching of the package location
can also mean that CMake might not get an opportunity to become aware of a
newly added package in a more preferable location. For example, the operating
system might come with a fairly old version of a package pre-installed. The
first time CMake is run on a project, it finds that old version and stores its
location in the cache. The user sees that an old version is being used and
decides to install a newer version of the package under some other directory,
adds that location to CMAKE_PREFIX_PATH and re-runs CMake. In this scenario,
the old version will still be used because the cache still points to the older
package’s location. The <packageName>_DIR cache entry would need to be
removed or the old version uninstalled before the newer version’s location
would be considered.
另一种控制可用于影响特定包裹的处理。通过
在项目早期将变量设置为 true(最好是在顶层或作为缓存变量),可以禁用给find_package()
定的每个非必需调用。这可以被认为是关闭可选包的一种方法,防止通过调用找到它。请注意,如果此类调用包含关键字,则不会阻止此类调用。packageNameCMAKE_DISABLE_FIND_PACKAGE_<packageName>find_package()REQUIRED
One further control is available to influence the handling of specific
packages. It is possible to disable every non-REQUIRED call to find_package()
for a given packageName by setting the
CMAKE_DISABLE_FIND_PACKAGE_<packageName> variable to true early in the
project, ideally at the top level or as a cache variable. This can be thought
of as a way of turning off an optional package, preventing it from being found
via find_package() calls. Note that it will not prevent such calls if they
include the REQUIRED keyword.
包往往可以在标准系统位置或 CMake 已告知的目录CMAKE_PREFIX_PATH或类似目录中找到。对于非系统软件包,如果它们不共享公共安装前缀,则必须指定每个软件包的位置可能会很乏味或不受欢迎。CMake 支持一种包注册表形式,允许将任意位置的引用收集到一个地方。这允许用户维护一个帐户或系统范围的注册表,CMake 将自动查阅该注册表,而无需进一步指示。注册表引用的位置不必是完整的包安装,它们也可以是包的构建树中的目录(或任何其他目录),只要所需的文件在那里即可。
Packages tend to be found either in standard system locations or in directories
CMake has been told about through CMAKE_PREFIX_PATH or similar. For
non-system packages, it can be tedious or undesirable to have to
specify the location for each package if they don’t all share a common install
prefix. CMake supports a form of package registry which allows references to
arbitrary locations to be collected together in one place. This allows the user
to maintain an account- or system-wide registry which CMake will consult
automatically without further direction. The locations referenced by the
registry don’t have to be a full package install, they can also be a directory
within a build tree for the package (or any other directory for that matter) as
long as the required files are there.
在 Windows 上,提供了两个注册表。用户注册表存储在 Windows 注册表中的该HKEY_CURRENT_USER键下,而系统包注册表存储在以下位置HKEY_LOCAL_MACHINE:
On Windows, two registries are provided. A user registry is stored in the
Windows registry under the HKEY_CURRENT_USER key, while a system package
registry is stored under HKEY_LOCAL_MACHINE:
HKEY_CURRENT_USER\Software\Kitware\CMake\Packages\<包名称>\ HKEY_LOCAL_MACHINE\Software\Kitware\CMake\Packages\<包名称>\
HKEY_CURRENT_USER\Software\Kitware\CMake\Packages\<packageName>\ HKEY_LOCAL_MACHINE\Software\Kitware\CMake\Packages\<packageName>\
对于给定的packageName,该点下的每个条目都是一个包含REG_SZ值的任意名称。该值应该是可以在其中找到该包的配置文件的目录。在 Unix 平台上,没有系统包注册表,只有存储在用户主目录下的用户包注册表,并且该点下的条目与 Windows 具有相同的含义:
For a given packageName, each entry under that point is an arbitrary name
holding a REG_SZ value. The value is expected to be a directory in which a
config file for that package can be found. On Unix platforms, there is no
system package registry, only a user package registry stored under the user’s
home directory and entries under that point have the same meaning as for
Windows:
〜/.cmake/packages/<包名称>/
~/.cmake/packages/<packageName>/
对于如何在任何平台上实际创建这些条目,CMake 提供的帮助非常少。没有为已安装的包提供自动化机制,但export()可以在项目CMakeLists.txt
文件中使用该命令将项目构建树的一部分添加到用户注册表中:
CMake provides very little assistance with how to actually create these entries
on any platform. No automated mechanism is provided for installed packages, but
the export() command can be used within a project’s CMakeLists.txt
files to add parts of a project’s build tree to the user registry:
export(PACKAGE packageName)export(PACKAGE packageName)
此命令可以将指定的包添加到用户包注册表中,并将该注册表项指向与调用的位置关联的当前二进制目录export()(请参阅下文了解可以阻止这种情况的条件)。然后由项目来确保该目录中存在该包的适当配置文件。如果不存在这样的配置文件,并且
find_package()为任何项目调用该包,则在权限允许的情况下,注册表项将被自动删除。包注册表中每个条目的名称通常是它指向的目录路径的 MD5 哈希值。这可以避免名称冲突,并且是该命令所采用的命名策略export(PACKAGE)。
This command can add the specified package to the user package registry and
point that registry entry to the current binary directory associated with
wherever export() was called (see below for conditions that can prevent this).
It is then up to the project to ensure that an appropriate config file for the
package exists in that directory. If no such config file exists and a
find_package() call is made for that package for any project, the registry
entry will be automatically removed if permissions allow it. It is common
practice for the name of each entry in the package registry to be the MD5 hash
of the directory path it points to. This avoids name collisions and is the
naming strategy employed by the export(PACKAGE) command.
将构建树中的位置添加到包注册表有其危险。虽然export(PACKAGE)可以向注册表添加位置,但除了手动删除注册表项或从构建目录中删除包配置文件之外,没有相应的机制可以再次删除它。很容易忘记这样做,因此过去实验留下的旧构建树很容易被意外地捡起。使用
export(PACKAGE)还可能对持续集成系统造成严重破坏,因为它会让项目拾取在同一台机器上构建的其他项目的构建树。
Adding locations from a build tree to the package registry has its dangers.
While export(PACKAGE) is available to add a location to the registry, there
is no corresponding mechanism to remove it again other than to manually delete
the registry entry or to remove the package config file from the build
directory. It can be easy to forget to do this, so an old build tree left
behind from past experiments can easily be picked up unexpectedly. The use of
export(PACKAGE) also has the potential to play havoc with continuous
integration systems by making projects pick up build trees of other projects
built on the same machine.
由于与 相关的危险export(PACKAGE),开发人员经常希望禁用它。CMake 提供了两种方法来实现此目的,一种是使用自 CMake 3.1 以来可用的选择退出方法,另一种是 CMake 3.15 中引入的使用高级选择加入机制的方法。对于 CMake 3.14 或更早版本,该export(PACKAGE)命令将修改包注册表,除非该CMAKE_EXPORT_NO_PACKAGE_REGISTRY变量设置为 true。由于默认情况下该变量未定义,因此该export(PACKAGE)命令将默认修改包注册表。在 CMake 3.15 中,默认行为已通过策略更改CMP0090,以便当该策略设置为 时NEW,该export(PACKAGE)命令将被禁用,除非该CMAKE_EXPORT_PACKAGE_REGISTRY变量设置为 true(请注意不同的变量名称)。如果策略CMP0090设置为OLD或未设置,则使用 CMake 3.14 及更早版本的行为。对于大多数实际场景,开发人员可以设置
CMAKE_EXPORT_NO_PACKAGE_REGISTRY为 true,无论策略设置或 CMake 版本如何,该export(PACKAGE)命令都将被禁用。
Because of the dangers associated with export(PACKAGE), developers will
frequently want to disable it.
CMake provides two ways to achieve this, one using an opt-out method available
since CMake 3.1 and another introduced in CMake 3.15 which uses a superior
opt-in mechanism.
With CMake 3.14 or earlier, the export(PACKAGE) command will modify the
package registry unless the CMAKE_EXPORT_NO_PACKAGE_REGISTRY variable is
set to true.
Because that variable is undefined by default, the export(PACKAGE) command
will modify the package registry by default.
In CMake 3.15, the default behavior was changed via policy CMP0090 such that
when that policy is set to NEW, the export(PACKAGE) command will be
disabled unless the CMAKE_EXPORT_PACKAGE_REGISTRY variable is set to
true (note the different variable name).
If policy CMP0090 is set to OLD or is not set, then the CMake 3.14 and
earlier behavior is used.
For most practical scenarios, developers can set
CMAKE_EXPORT_NO_PACKAGE_REGISTRY to true and regardless of policy settings or
CMake version, the export(PACKAGE) command will be disabled.
CMAKE_EXPORT_NO_PACKAGE_REGISTRY与CMAKE_EXPORT_PACKAGE_REGISTRY
控制对注册表的写入不同,CMake 提供了一组单独的变量来控制对注册表的读取。和变量可用于指定调用的默认行为,分别控制是否从用户和系统包注册表中读取CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY。
CMake 3.16 弃用了这两个变量,将其替换为
和 ,
以与本章前面提到的其他变量保持更一致的命名和行为。这些较新的变量优先于同时定义了新旧变量的旧变量。CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRYfind_package()CMAKE_FIND_USE_PACKAGE_REGISTRYCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRYCMAKE_FIND_USE_…
Whereas CMAKE_EXPORT_NO_PACKAGE_REGISTRY and CMAKE_EXPORT_PACKAGE_REGISTRY
control writing to the registry, CMake provides a separate set of variables to
control reading from it.
The CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY and
CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY variables can be used to
specify the default behavior of find_package() calls, controlling whether to
read from the user and system package registries respectively.
CMake 3.16 deprecated both variables, replacing them with
CMAKE_FIND_USE_PACKAGE_REGISTRY and
CMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY to maintain more consistent naming
and behavior with the other CMAKE_FIND_USE_… variables mentioned earlier
in this chapter.
These newer variables take precedence over the old ones where both the old and
new variables are defined.
虽然上述两组CMAKE_EXPORT_…和变量是互补的,但这些变量在将构建与包注册表隔离方面更有效,并且通常与开发人员更相关。CMAKE_FIND_…CMAKE_FIND_…
While the two sets of CMAKE_EXPORT_… and CMAKE_FIND_… variables
described above are complementary, the CMAKE_FIND_… variables are more
effective at isolating a build from the package registry and are usually more
relevant for developers.
在实践中,包注册表并不经常使用。为添加和删除条目提供的帮助有限,这意味着维护注册表在某种程度上是一个手动过程。当通过主机的标准包管理系统安装包时,可以想象它可以根据需要将自身添加到系统或用户注册表中,然后包的卸载程序可以删除相同的条目。虽然包位置定义良好并且其定义在概念上也很简单,但很少有包费心去完成注册和取消注册的工作。包可能以各种不同的方式进入最终用户的机器,这使得稳健且简单地实现此类注册/注销功能有些困难。
In practice, package registries are not often used. The limited help provided for adding and removing entries means maintaining the registry is somewhat of a manual process. When a package is installed via the host’s standard package management system, it could conceivably add itself to either the system or user registry as appropriate, then the package’s uninstaller could remove that same entry. While the package locations are well defined and their definition is conceptually easy, few packages bother to do the work to register and unregister themselves. The various different ways a package may find its way onto an end user’s machine makes it somewhat difficult to implement such register/unregister features robustly and simply.
该find_package()命令通常是查找包并将其合并到 CMake 项目中的首选方法,但在某些情况下,结果可能不太理想。一些查找模块尚未更新为更现代的实践,并且不提供导入的目标,而是依赖于定义使用项目必须手动处理的变量集合。其他模块可能落后于最新的软件包版本,导致不兼容或提供不正确的信息。
The find_package() command will generally be the preferred method for finding
and incorporating a package into a CMake project, but in certain cases the
results can be less than ideal. Some Find modules are yet to be updated to more
modern practices and do not provide imported targets, relying instead on defining
a collection of variables that consuming projects must handle manually. Other
modules may fall behind the latest package releases, leading to
incompatibilities or incorrect information being provided.
在某些情况下,软件包可能支持,这是一种提供类似信息但形式不同的pkg-config工具。find_package()如果此类pkg-config详细信息可用,则PkgConfig可以使用 Find 模块来读取该信息并以更 CMake 友好的方式提供它。导入的目标可以自动创建,使项目不必手动处理各种变量。详细信息pkg-config也可能与软件包的已安装版本相匹配,因为它们通常由软件包本身提供。
In some instances, a package may have support for pkg-config, a tool that
provides similar information to find_package() but in a different form. If
such pkg-config details are available, then the PkgConfig Find module may
be used to read that information and provide it in a more CMake-friendly way.
Imported targets can be automatically created, freeing projects from having to
handle various variables manually. The pkg-config details are also likely to
match the installed version of the package, since they are typically provided
by the package itself.
该FindPkgConfig模块定位可执行pkg-config文件并定义一些调用它的函数来查找和提取有关pkg-config支持的包的详细信息。如果模块找到可执行文件,它将
PKG_CONFIG_FOUND变量设置为 true,并将PKG_CONFIG_EXECUTABLE变量设置为工具的位置。也PKG_CONFIG_VERSION_STRING设置为工具的版本(2.8.8 之前的 CMake 版本除外)。
The FindPkgConfig module locates the pkg-config executable and defines a
few functions that invoke it to find and extract details about packages that
have pkg-config support. If the module finds the executable, it sets the
PKG_CONFIG_FOUND variable to true and the PKG_CONFIG_EXECUTABLE variable to
the location of the tool. The PKG_CONFIG_VERSION_STRING is also set to the
tool’s version (except for CMake versions before 2.8.8).
在实践中,项目应该很少需要使用该PKG_CONFIG_EXECUTABLE
变量,因为该模块还定义了两个包装该工具的函数,以提供更方便的方式来查询包详细信息。这两个函数
pkg_check_modules()和pkg_search_module()接受完全相同的选项集并具有相似的行为。两者之间的主要区别在于
pkg_check_modules()检查其参数列表中给出的所有模块,而在pkg_search_module()发现满足条件的第一个模块处停止。术语“模块”而不是“包”的使用是在这些命令的历史中建立的,可能会导致一些混乱,但它们与常规 CMake 模块没有直接关系,本质上可以被视为包。
In practice, projects should rarely need to use the PKG_CONFIG_EXECUTABLE
variable, since the module also defines two functions which wrap the tool to
provide a more convenient way to query package details. These two functions,
pkg_check_modules() and pkg_search_module(), accept exactly the same set of
options and have similar behavior. The main difference between the two is that
pkg_check_modules() checks all the modules given in its argument list,
whereas pkg_search_module() stops at the first one it finds that satisfies
the criteria. The use of the term module rather than package is established
in the history of these commands and may cause some confusion, but they have no
direct relationship to regular CMake modules and can essentially be thought of
as packages.
pkg_check_modules(prefix
[REQUIRED] [QUIET]
[IMPORTED_TARGET [GLOBAL] ]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
moduleSpec1 [moduleSpec2...]
)
pkg_search_module(prefix
[REQUIRED] [QUIET]
[IMPORTED_TARGET [GLOBAL] ]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
moduleSpec1 [moduleSpec2...]
)pkg_check_modules(prefix
[REQUIRED] [QUIET]
[IMPORTED_TARGET [GLOBAL] ]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
moduleSpec1 [moduleSpec2...]
)
pkg_search_module(prefix
[REQUIRED] [QUIET]
[IMPORTED_TARGET [GLOBAL] ]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
moduleSpec1 [moduleSpec2...]
)
这些函数的行为与find_package(). 和REQUIRED参数QUIET在这里具有与命令相同的效果find_package()。在 CMake 3.1 或更高版本中,CMAKE_PREFIX_PATH、
CMAKE_FRAMEWORK_PATH和CMAKE_APPBUNDLE_PATH也都以相同的方式被视为搜索位置,并且NO_CMAKE_PATH和
NO_CMAKE_ENVIRONMENT_PATH关键字在这里也具有相同的含义。可以设置该
PKG_CONFIG_USE_CMAKE_PREFIX_PATH变量来更改是否考虑这些搜索位置的默认行为(它将被视为打开或关闭搜索位置的布尔开关),但项目通常应避免它,除非需要支持 CMake 3.1 之前的版本。
The behavior of these functions has some similarities to find_package().
The REQUIRED and QUIET arguments have the same effect here as they do for
the find_package() command. With CMake 3.1 or later, CMAKE_PREFIX_PATH,
CMAKE_FRAMEWORK_PATH and CMAKE_APPBUNDLE_PATH are all considered as search
locations in the same way too and the NO_CMAKE_PATH and
NO_CMAKE_ENVIRONMENT_PATH keywords also have the same meaning here. The
PKG_CONFIG_USE_CMAKE_PREFIX_PATH variable can be set to change the
default behavior for whether or not these search locations are considered (it
will be treated as a boolean switch to turn the search locations on or off),
but projects should generally avoid it unless they need to support CMake
versions older than 3.1.
IMPORTED_TARGET仅 CMake 3.6 或更高版本支持该选项。给出后,如果找到请求的模块,则
PkgConfig::<prefix>创建具有该名称的导入目标。此导入的目标将具有从模块.pc文件填充的接口详细信息,提供标头搜索路径、编译器标志等内容。因此,如果项目所需的最低 CMake 版本为 3.6,则强烈建议使用此选项或稍后。如果使用 CMake 3.13 或更高版本,GLOBAL还可以添加关键字以使导入的目标具有全局可见性,而不仅仅是当前目录范围及以下目录范围。
The IMPORTED_TARGET option is only supported with CMake 3.6 or later. When
given, if the requested module is found then an imported target with the name
PkgConfig::<prefix> is created. This imported target will have interface
details populated from the module’s .pc file, providing such things as header
search paths, compiler flags, etc. For this reason, it is highly recommended
that this option be used if the minimum CMake version required by the project
is 3.6 or later. If using CMake 3.13 or later, the GLOBAL keyword can also
be added to make imported targets have global visibility instead of only to
the current directory scope and below.
这些函数需要一个或多个moduleSpec参数来定义要搜索的内容。这些可以是裸模块/包名称,也可以将名称与版本要求结合起来。此类版本要求的形式name=version为name<=version或
name>=version。使用 CMake 3.13 或更高版本,<也>支持。当不包含版本要求时,任何版本都被接受。
The functions expect one or more moduleSpec arguments to define what to
search for.
These can be a bare module/package name or they can combine the name with a
version requirement.
Such version requirements have the form name=version, name<=version or
name>=version.
With CMake 3.13 or later, < and > are also supported.
When no version requirement is included, any version is accepted.
pkg-config返回后,函数通过使用适当的选项调用来提取包详细信息的相关部分,从而在调用范围中设置许多变量。当一组选项返回多个项目(例如多个库或多个搜索路径)时,相应的变量将保存一个 CMake 列表。
Upon return, the functions set a number of variables in the calling scope by
calling pkg-config with the appropriate option(s) to extract the relevant
part of the package details.
Where multiple items are returned by a set of options (e.g. multiple libraries
or multiple search paths), the corresponding variable will hold a CMake list.
| 多变的 | pkg-config使用的选项 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
仅当满足模块要求时才设置上述变量。检查这一点的规范方法是使用prefix_FOUND和
prefix_STATIC_FOUND变量。对于pkg_check_modules(),必须满足这些变量的所有moduleSpec
要求才能具有 true 值,而pkg_search_module()只需找到一个匹配的moduleSpec。使用 CMake 3.16 或更高版本,使用找到的模块pkg_search_module()填充
。<prefix>_MODULE_NAME
The above variables are only set if the module requirements are satisfied. The
canonical way to check this is using the prefix_FOUND and
prefix_STATIC_FOUND variables. For pkg_check_modules(), all moduleSpec
requirements must be satisfied for these variables to have a value of true,
whereas pkg_search_module() only has to find one matching moduleSpec.
With CMake 3.16 or later, pkg_search_module() populates the
<prefix>_MODULE_NAME with the module that was found.
对于pkg_check_modules(),当成功找到模块时,还会设置一些附加的每个模块变量。下文中,若只moduleSpec
给出 1 则YYY = prefix,否则YYY = prefix_moduleName。
For pkg_check_modules(), some additional per-module variables are also set
when modules are found successfully. In the following, if only one moduleSpec
is given then YYY = prefix, otherwise YYY = prefix_moduleName.
YYY_VERSION
YYY_VERSION
--modversion选项的输出中提取。
--modversion option.
YYY_PREFIX
YYY_PREFIX
prefix,大多数.pc文件通常都会定义该变量,并且
pkg-config默认情况下会提供该变量。
prefix, which most .pc files typically define and which
pkg-config provides by default anyway.
YYY_INCLUDEDIR
YYY_INCLUDEDIR
includedir。这是一个常见但不是必需的变量。
includedir.
This is a common but not required variable.
YYY_LIBDIR
YYY_LIBDIR
libdir。同样,这是一个常见但不是必需的变量。
libdir.
Again, this is a common but not required variable.
在 CMake 3.4 及更高版本中,该FindPkgConfig模块提供了一个附加函数,可用于从.pc文件中提取任意变量:
In CMake 3.4 and later, the FindPkgConfig module provides an additional
function which can be used to extract arbitrary variables from .pc files:
pkg_get_variable(resultVar moduleName variableName)pkg_get_variable(resultVar moduleName variableName)
这在内部用于pkg_check_modules()查询prefix、
includedir和libdir变量,但项目可以使用它来查询任何任意变量的值。请注意,在 CMake 3.15 之前,pkg_get_variable()包含一个导致其有效忽略 的错误CMAKE_PREFIX_PATH,因此如果依赖此功能,请考虑将 CMake 3.15 设为最低版本。
This is used internally by pkg_check_modules() to query the prefix,
includedir and libdir variables, but projects can use it to query the value
of any arbitrary variable.
Note that before CMake 3.15, pkg_get_variable() contained a bug which
resulted in it effectively ignoring CMAKE_PREFIX_PATH, so consider making
CMake 3.15 the minimum version if relying on this functionality.
对于大多数常见系统,模块提供的功能FindPkgConfig工作得相当可靠。然而,这些函数的实现确实依赖于pkg-config版本 0.20.0 中引入的功能。一些较旧的系统(例如Solaris 10)附带较旧的版本,这pkg-config会导致对函数的所有调用都FindPkgConfig无法成功找到任何模块,并且不会记录任何错误消息来突出显示版本pkg-config太旧。
For most common systems, the functions provided by the FindPkgConfig module
work fairy reliably. The implementations of those functions do, however, rely
on features introduced in pkg-config version 0.20.0. Some older systems (e.g.
Solaris 10) come with older versions of pkg-config which result in all calls
to the FindPkgConfig functions failing to find any modules successfully and
no error message is logged to highlight that the pkg-config version is too
old.
正如前面几节所演示的,CMake 搜索各种find_…()命令的位置和名称的逻辑很复杂。当搜索返回意外结果或未能找到被认为存在的内容时,找出问题所在可能并非易事。为了解决这个问题,CMake 3.17 添加了一个新的--debug-find命令行选项,可以记录对内置find_…()命令的调用。此输出可能包括搜索设置的简要摘要以及已检查的每个位置和名称的列表。如果find_…()命令调用使用缓存值而不是实际执行搜索,则该调用可能不会产生调试输出。
As the preceding sections demonstrate, the logic for the locations and names
CMake searches for the various find_…() commands is complex.
When a search returns something unexpected or it fails to find something that
was thought to exist, it can be non-trivial to work out what went wrong.
To help with this, CMake 3.17 added a new --debug-find command-line option
which enables logging for calls to the built-in find_…() commands.
This output may include a brief summary of the search settings and a list of
each location and name that was checked.
If a find_…() command call uses a cached value rather than actually
performing a search, that call may produce no debug output.
该--debug-find选项适用于整个构建,因此对于具有许多find_…()调用的大型项目,详细的输出可能会令人难以承受。如果开发人员只想针对项目的特定调用或部分,更有效的策略是仅find_…()围绕感兴趣的特定调用启用命令调试。CMAKE_FIND_DEBUG_MODE
这可以通过在感兴趣的调用之前将调用的变量设置为 true 并在调用之后设置为 false 来实现(CMake 3.17 中也添加了对此变量的支持)。例如:
The --debug-find option applies to the whole build, so for large projects
with many find_…() calls, the verbose output can be overwhelming.
If developers only want to target a specific call or section of a project,
a more effective strategy is to only enable find_…() command debugging
around the specific calls of interest.
This can be achieved by setting a variable called CMAKE_FIND_DEBUG_MODE
to true before the calls of interest and to false after them (support for this
variable was also added in CMake 3.17).
For example:
set(CMAKE_FIND_DEBUG_MODE TRUE)
find_program(...)
set(CMAKE_FIND_DEBUG_MODE FALSE)set(CMAKE_FIND_DEBUG_MODE TRUE)
find_program(...)
set(CMAKE_FIND_DEBUG_MODE FALSE)
调试输出旨在作为供人类使用的开发辅助工具。它不应用作任何脚本或其他形式的自动化处理的输入,因为格式和内容可能会从一个 CMake 版本更改为另一个 CMake 版本。
The debugging output is meant as a development aid for human consumption. It should not be used as input to any script or other form of automated processing, since the format and contents could change from one CMake version to another.
从 CMake 3.0 开始,我们有意识地转向使用导入的目标来表示外部库和程序,而不是填充变量。这允许将此类库和程序视为一个连贯的单元,不仅收集相关二进制文件的位置,而且在库的情况下,还收集相关的标头搜索路径、编译器定义以及使用目标所需的进一步库依赖项也是导入目标的一部分。这使得外部库和程序在项目中像项目定义的任何其他常规目标一样易于使用。这种焦点的转变意味着查找包变得比查找单个文件、路径等重要得多,并且越来越多的项目希望将自己作为包供其他 CMake 项目使用。查找单个文件等仍然有其用途,并且有助于理解如何做到这一点,但开发人员应该将其视为包和/或导入目标的垫脚石,而不是其本身的目的。只要有可能,更喜欢查找包而不是包中的单个内容。
From CMake 3.0, there has been a conscious shift toward the use of imported targets to represent external libraries and programs rather than populating variables. This allows such libraries and programs to be treated as a coherent unit, collecting together not just the location of the relevant binary, but in the case of libraries, the associated header search paths, compiler defines and further library dependencies that consuming targets will need are also part of the imported target. This makes external libraries and programs as easy to use within a project as any other regular target the project defines. This shift in focus means that finding packages has become much more important than finding individual files, paths, etc. and there is an increasing push for projects to make themselves consumable by other CMake projects as packages. Finding individual files, etc. still has its uses and it is helpful to understand how that can be done, but developers should see it as a stepping stone to packages and/or imported targets rather than an end in itself. Wherever possible, prefer to find packages rather than individual things within packages.
查找软件包时,出现的大多数复杂情况都与多个版本安装在不同位置的情况有关。用户可能不知道所有已安装的版本,或者可能期望应该在其他版本之前找到哪个版本。与项目尝试预测此类情况相比,通常更建议不要偏离默认搜索行为太远,并让用户通过缓存或环境变量提供自己的覆盖。CMAKE_PREFIX_PATH通常是最方便的方法,因为 CMake 会自动搜索列出的每个前缀路径下的一系列常见目录布局。
When finding packages, most complications that arise are related to situations
where multiple versions are installed in different locations. The user may not
be aware of all the installed versions or there may be expectations about which
one should be found ahead of the others. Rather than the project trying to
predict such situations, it is generally more advisable to not deviate too far
from the default search behavior and let the user provide their own overrides
via cache or environment variables. CMAKE_PREFIX_PATH is usually the most
convenient way to do this due to the way CMake automatically searches a range
of common directory layouts below each prefix path listed.
不要过度依赖版本范围对find_package(). 此功能仅适用于 CMake 3.19 或更高版本,较旧的包通常会忽略版本范围约束的上限。将上限视为建议性的,而不是严格执行的。
Do not rely heavily on the version range support for find_package().
This feature is only available with CMake 3.19 or later and older packages
will typically ignore the upper end of the version range constraint.
Consider the upper limit to be advisory rather than strictly enforced.
find_…()除此以外的所有命令都find_package()以类似的方式工作。默认情况下,它们会缓存成功的结果,以避免下次find_…()要求命令查找相同内容时必须重复整个查找操作。即使在多个 CMake 调用中也会进行缓存。find_…()考虑到每个调用可能搜索的潜在大量位置和目录条目,当整个项目中有许多此类调用时,缓存机制可以节省大量时间。然而,开发人员需要意识到至少有两个后果。首先,一旦find_file()、find_path()或命令成功,它将停止搜索所有后续调用find_program(),
find_library()即使运行该命令将返回不同的结果或者先前找到的实体不再存在。如果实体被删除,这可能会导致构建错误,只能通过从缓存中删除过时的条目来纠正该错误。开发人员通常只是删除整个缓存并从头开始重新构建,而不是尝试找出需要删除哪些缓存变量。开发人员应该注意的这种查找行为的另一个方面是,如果对这些命令之一的调用find_…()无法找到所需的实体,则每次调用都会重复搜索,即使在同一项目中也是如此。不成功的调用不会被缓存。如果一个项目有很多这样的调用,这可能会减慢配置步骤。因此,开发人员应该仔细考虑项目如何使用
find_…()命令,并尽量减少搜索不成功的可能性和数量。如果最低 CMake 版本可以设置为 3.21 或更高版本,策略CMP0125还可以避免一些微妙的令人惊讶的行为。
All the find_…() commands except find_package() work in a similar way.
By default, they cache a successful result to avoid having to repeat the whole
find operation the next time the find_…() command is asked to find the
same thing.
This is cached even across multiple CMake invocations.
Given the potentially large number of locations and directory entries each
call may search through, the caching mechanism can save a non-trivial amount
of time when there are many such find_…() invocations throughout the
project.
There are, however, at least two consequences of this that developers need to
be aware of.
Firstly, once a find_file(), find_path(), find_program() or
find_library() command succeeds, it will stop searching for all subsequent
invocations, even if running the command would return a different result or if
the entity found previously no longer exists.
If the entity is removed, this can result in build errors that can only be
rectified by removing the out-of-date entries from the cache.
Developers often just delete their entire cache and rebuild again from scratch
rather than trying to figure out which cache variables need to be removed.
The other aspect of this find behavior that developers should be aware of is
that where a call to one of these find_…() commands fails to find the
desired entity, the search will be repeated for every call, even within the
same project.
An unsuccessful call is not cached.
If a project has many such calls, this can slow down the configure step.
Developers should therefore carefully consider how the project uses
find_…() commands and try to minimize the likelihood and number of
unsuccessful searches.
If the minimum CMake version can be set to 3.21 or later, policy CMP0125 also
allows some subtle surprising behaviors to be avoided.
情况find_package()有点复杂。如果通过查找模块找到包,则上述所有问题很可能也适用于该包,因为逻辑可能建立在其他find_…()命令之上。如果通过配置模式找到包,则将find_package()缓存成功的结果并在后续调用时首先检查该位置。如果包在该位置不再有适当的配置文件,则该命令将继续执行其正常搜索逻辑。配置模式的这种独特行为更加稳健,并且更接近开发人员自然想要的。
The situation with find_package() is a little more complicated. If the
package is found via a Find module, then it is likely that all the above
concerns will also apply to the package, since the logic is likely to be built
upon the other find_…() commands. If the package is instead found via
config mode, then find_package() will cache a successful result and check
that location first on subsequent invocations. If the package no longer has an
appropriate config file at that location, the command proceeds with its normal
search logic. This unique behavior for config mode is much more robust and is
closer to what developers would naturally want.
在持续集成系统中,结果缓存find_…()可能会导致微妙的问题,这是一个特别棘手的情况。如果在保留先前运行的 CMake 缓存的情况下使用增量构建,则项目中对其搜索内容的方式所做的更改可能不会反映在构建中。只有清除 CMake 缓存后,此类更改才会生效。缓存通常还意味着不会记录有关所找到实体的详细信息,因此构建输出几乎无法提供有关旧搜索详细信息的使用的线索。因此,人们可能会要求所有 CI 构建都从头开始构建,但这对于较长的构建来说可能不可行。可能有助于减少问题的策略是在 CI 负载较低时安排每日构建作业,其中构建树被清除,然后按正常方式构建项目。这仍然会在正常时间内保持增量行为,并且通常会使任何与缓存相关的问题在一天内自行解决。在分支上进行更改并且 CI 构建在该分支与其他分支之间交替的时期,此策略的有效性会降低,但人们希望这种时期不常见,并且只要开发人员意识到这一点就可以容忍在那段时间的潜在后果。
A particularly tricky situation where the caching of find_…() results can
lead to subtle problems is with continuous integration systems. If incremental
builds are being used where the CMake cache of a previous run is kept, then
changes made in a project to the way it searches for things might not be
reflected in the build. Only when the CMake cache is cleared might such changes
take effect. The caching often also means that no details are logged about the
entity being found, so the build output gives little clue about the use of the
old search details. One might therefore be tempted to require all CI builds to
build from scratch, but this may not be feasible for longer builds. A strategy
which may help reduce the problem is to schedule a daily build job at a time of
low CI load where the build tree is cleared and then the project is built as
per normal. This will still keep the incremental behavior during regular hours
and it will usually make any cache-related problems self-resolving within a
day. The effectiveness of this strategy is reduced during periods where changes
are being made on a branch and CI builds are alternating between that branch
and other branches, but one would hope that such periods are not common and can
be tolerated as long as developers are made aware of potential consequences
during that time.
find_package()应谨慎使用该命令的包注册表功能。它们有可能为持续集成系统带来意想不到的结果,其中项目可能希望找到也在同一台机器上构建的包。不幸的是,没有可以设置为禁用注册表的环境变量,但可以通过将 CMake 变量设置
CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY为OFF(CI 作业通常没有修改系统包注册表所需的权限)来由项目本身强制执行,因此CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY也不需要设置)。实际上,很少有项目会写入包注册表,因此除非知道此类项目可能正在使用 CI 系统,否则将此 CMake 变量添加到每个可能受影响的项目的需要很低。项目还应该避免在 CI 作业中进行调用export(PACKAGE)
(可以说它们通常应该避免此类调用)。
The package registry features of the find_package() command should be
approached with caution. They have the potential to give unexpected results for
continuous integration systems where projects may want to find packages that
are also built on the same machine. Unfortunately, there is no
environment variable that can be set to disable the use of the registries, but
it can be enforced by the projects themselves by setting the
CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY CMake variable to OFF (CI jobs would
not normally have the required permissions to modify the system package
registry, so setting CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY as well
should be unnecessary). In practice, few projects write into the package
registry, so unless it is known that such a project might be using the CI
system, the need to add this CMake variable to every potentially affected
project is low. Projects should also avoid making calls to export(PACKAGE)
within CI jobs (arguably they should avoid such calls in general).
仅在不适合的FindPkgConfig情况下才使用该模块。find_package()通常,这是针对 CMake 提供查找模块的包,但该查找模块相当旧并且不提供导入的目标,或者它落后于最新的包版本。该FindPkgConfig模块对于搜索 CMake 一无所知的包以及包不提供自己的 CMake 配置文件但确实提供pkg-config(即.pc)文件的情况也很有用。
Use of the FindPkgConfig module should be reserved only for those situations
where find_package() is not suitable. Typically this is for a package where
CMake provides a find module, but that find module is fairly old and does not
provide imported targets, or where it falls behind the more recent package
releases. The FindPkgConfig module is also useful for searching for packages
that CMake knows nothing about and where the package does not provide its own
CMake config file, but it does provide a pkg-config (i.e. .pc) file.
当使用工具链文件进行交叉编译时,更喜欢设置
CMAKE_SYSROOT而不是CMAKE_FIND_ROOT_PATH. find_…()虽然两者以相同的方式
影响各种命令的搜索路径,CMAKE_SYSROOT但也仅确保正确增强编译器和链接器标志,以便标头包含和库链接正常工作。
When using a toolchain file for cross-compilation, prefer to set
CMAKE_SYSROOT rather than CMAKE_FIND_ROOT_PATH. While both affect the
search paths of the various find_…() commands in the same way, only
CMAKE_SYSROOT also ensures that the compiler and linker flags are properly
augmented so that header inclusions and library linking work correctly.
在交叉编译场景中,搜索程序通常希望找到将在主机上运行的二进制文件,而搜索文件和库通常希望找到目标的内容。因此,在工具链文件中经常会看到以下内容来默认强制执行此类行为:
In cross-compiling scenarios, it is also typical that searches for programs expect to find binaries that will run on the host, whereas searches for files and libraries typically expect to find things for the target. Therefore, it is very common to see the following in toolchain files to enforce such behavior by default:
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
有人可能会争辩说,这应该在项目中设置,而不是依赖于在工具链文件中设置。从技术上讲,开发人员可以根据需要自由使用任何工具链文件,并且该项目隐式依赖于默认行为,然后选择覆盖或不覆盖。project()这里增加的复杂性是工具链文件可能会为每个或调用重新读取
enable_language(),因此如果项目想要强制执行默认值的特定组合,则必须在每次此类调用后执行此操作。因此,一个合理的妥协是项目在第一次调用之前包含上述块,project()并且工具链编写者也包含它。然后,如果工具链作者不包含这样的块,至少该项目仍然获得合理的默认值。如果工具链文件将默认值更改为其他内容,它们将在整个项目中一致应用。开发人员应该非常谨慎地使用上面示例中显示的设置之外的设置,因为它是一种比项目经常假设的常见模式。
One could argue that this should be set in the project rather than relying on
it being set in a toolchain file.
Technically the developer is free to use any toolchain file if they wish and it
is the project that implicitly relies on default behavior that it then chooses
to override or not.
An added complexity here is that toolchain files may be re-read for each
project() or enable_language() call, so if a project wants to enforce a
particular combination of defaults, it would have to do so after every such
call.
A reasonable compromise, therefore, is for projects to include the above
block before its first project() call and for toolchain writers to also
include it.
Then, if toolchain authors do not include such a block, at least the project
still gets sensible defaults.
If a toolchain file changes the defaults to something else, they will then be
applied consistently throughout the whole project.
Developers should be very wary of using settings other than those shown in the
example just above, since it is such a common pattern than projects frequently
assume it.
对于开发人员能够在设备和模拟器版本之间切换而无需重新运行 CMake 的情况(例如,在 iOS 项目中使用 Xcode 时),请避免调用find_library(). 通过此类调用获得的任何结果只能指向设备或模拟器库之一,而不能同时指向两者。在这种情况下,添加仅按名称链接而不按路径链接的底层链接器标志,例如-framework ARKit或-lz。如果在默认链接器搜索路径上找不到框架或库,则项目还需要提供链接器选项来扩展搜索路径以允许找到它们。
For situations where the developer is able to switch between device and
simulator builds without re-running CMake (e.g. when using Xcode for an iOS
project), avoid calls to find_library(). Any results obtained by such calls
can only ever point to one of either the device or simulator library, not both.
Add the underlying linker flags that link only by name and not by path in such
cases, such as -framework ARKit or -lz. If the frameworks or libraries
cannot be found on the default linker search path, the project will also need
to provide linker options to extend the search paths to allow them to be found.
在线示例和博客文章在是否使用CMAKE_MODULE_PATH或CMAKE_PREFIX_PATH
控制 CMake 搜索内容的位置上显示相互冲突的建议是很常见的。记住差异的一个简单方法是,CMAKE_MODULE_PATH仅在搜索
FindXXX.cmake文件或通过命令引入模块时由 CMake 使用include()。对于其他一切,包括搜索配置包文件,
CMAKE_PREFIX_PATH都会使用。
It is quite common for online examples and blog posts to show conflicting
recommendations over whether to use CMAKE_MODULE_PATH or CMAKE_PREFIX_PATH
to control where CMake searches for things. An easy way to remember the
difference is that CMAKE_MODULE_PATH is only used by CMake when searching for
FindXXX.cmake files or when a module is brought in via an include() command.
For everything else, including searching for config package files,
CMAKE_PREFIX_PATH is used.
构建项目的自然后续操作是测试它创建的工件。CMake 软件套件包括 CTest 工具,可用于自动化测试阶段,甚至配置、构建、测试甚至将结果提交到仪表板的整个过程。本章首先介绍如何使用 CMake 定义测试并使用ctest命令行工具执行它们的更简单的情况。自动化整个配置-构建-测试过程使用了大部分相同的知识,并将在本章后面讨论。
A natural follow-on to building a project is to test the artifacts it created.
The CMake software suite includes the CTest tool which can be used
to automate the testing phase, or even the entire process of configuring,
building, testing and even submitting results to a dashboard. This chapter
first covers the simpler case of how to use CMake to define tests and execute
them using the ctest command line tool. Automating the entire
configure-build-test process uses much of that same knowledge and is discussed
later in the chapter.
在 CMake 项目中定义测试的第一步是调用
enable_testing()顶级CMakeLists.txt文件中的某个位置。这通常会在第一次通话后不久就完成project()。此函数的作用是指示 CMake 在 中写出一个 CTest 输入文件,其中包含CMAKE_CURRENT_BINARY_DIR项目中定义的所有测试的详细信息(更准确地说,是在当前目录范围及以下目录中定义的那些测试)。可以在子目录中调用而不会出现错误,但是如果没有在顶层enable_testing()调用,则不会在构建树的顶部创建 CTest 输入文件,而这通常是预期的位置。enable_testing()
The first step to defining tests in a CMake project is to call
enable_testing() somewhere in the top level CMakeLists.txt file. This
would typically be done early, soon after the first project() call. The
effect of this function is to direct CMake to write out a CTest input file in
the CMAKE_CURRENT_BINARY_DIR with details of all the tests defined in the
project (more accurately, those tests defined in the current directory scope
and below). enable_testing() can be called in a subdirectory without error,
but without a call to enable_testing() at the top level, the CTest input file
will not be created at the top of the build tree, which is where it is normally
expected to be.
定义单独的测试是使用以下add_test()命令完成的:
Defining individual tests is done with the add_test() command:
add_test(NAME testName
COMMAND command [arg...]
[CONFIGURATIONS config1 [config2...]]
[WORKING_DIRECTORY dir]
[COMMAND_EXPAND_LISTS] # CMake 3.16 or later
)add_test(NAME testName
COMMAND command [arg...]
[CONFIGURATIONS config1 [config2...]]
[WORKING_DIRECTORY dir]
[COMMAND_EXPAND_LISTS] # CMake 3.16 or later
)
默认情况下,如果命令返回退出代码 0,则测试将被视为通过,但支持更灵活的通过/失败处理,并将在下一节中讨论。在 CMake 3.19 之前,testName不应包含任何空格、引号或其他特殊字符。对于 CMake 3.19 或更高版本,当策略CMP0110
设置为时,这些约束将被删除NEW。CMake 3.18.0 最初引入了相同的功能,但在发现它破坏了一些项目后,它在 3.18.1 中被恢复。该功能在 3.19.0 中再次重新引入,这次的策略是确保依赖旧行为的项目的向后兼容性。
By default, the test will be deemed to pass if the command returns an exit
code of 0, but more flexible pass/fail handling is supported and is discussed
in the next section.
Prior to CMake 3.19, the testName should not contain any spaces, quotes or
other special characters.
With CMake 3.19 or later, these constraints are removed when policy CMP0110
is set to NEW.
CMake 3.18.0 originally introduced the same capability, but it was reverted in
3.18.1 after it was found to break some projects.
The capability was reintroduced again in 3.19.0, this time with a policy to
ensure backward compatibility for projects that relied on the old behavior.
可以command是可执行文件的完整路径,也可以是项目中定义的可执行目标的名称。使用目标名称时,CMake 会自动替换可执行文件的真实路径。当使用多配置生成器(如 Xcode、Visual Studio 或 Ninja Multi-Config)时,这特别有用,其中可执行文件的位置取决于配置。
The command can be a full path to an executable or it can be the name of an
executable target defined in the project. When a target name is used, CMake
automatically substitutes the real path to the executable. This is
particularly useful when using multi configuration generators like Xcode,
Visual Studio or Ninja Multi-Config where the location of the executable will
be configuration-dependent.
cmake_minimum_required(VERSION 3.0)
project(CTestExample)
enable_testing()
add_executable(TestApp testapp.cpp)
add_test(NAME noArgs COMMAND TestApp)cmake_minimum_required(VERSION 3.0)
project(CTestExample)
enable_testing()
add_executable(TestApp testapp.cpp)
add_test(NAME noArgs COMMAND TestApp)
将目标自动替换为其真实位置不会扩展到命令参数,只有命令本身支持此类替换。如果需要将目标位置作为命令行参数给出,则可以使用生成器表达式。例如:
The automatic substitution of a target with its real location does not extend to the command arguments, only the command itself supports such substitution. If the location of a target needs to be given as a command line argument, generator expressions can be used. For example:
add_executable(App1 ...)
add_executable(App2 ...)
add_test(NAME WithArgs COMMAND App1 $<TARGET_FILE:App2>)add_executable(App1 ...)
add_executable(App2 ...)
add_test(NAME WithArgs COMMAND App1 $<TARGET_FILE:App2>)
运行测试时,用户可以指定应测试哪些配置。当项目使用单个配置生成器时,配置不必与构建类型匹配。特别是,如果未提供配置,则假定为空配置。如果没有可选CONFIGURATIONS关键字,则无论构建类型或用户请求什么配置,都将为所有配置运行测试。如果CONFIGURATIONS给出关键字,则仅针对列出的配置运行测试。请注意,空配置仍然被认为是有效的,因此为了在这种情况下运行测试,空字符串必须是列出的字符串之一CONFIGURATIONS。
When running the tests, the user can specify which configuration should be
tested. When the project is using a single configuration generator, the
configuration does not have to match the build type. In particular, if no
configuration is provided, an empty configuration is assumed. Without the
optional CONFIGURATIONS keyword, the test will be run for all configurations
regardless of the build type or what configuration has been requested by the
user. If the CONFIGURATIONS keyword is given, only for those configurations
listed will the test be run. Note that an empty configuration is still
considered valid, so for the test to run in that scenario, an empty string
would have to be one of the CONFIGURATIONS listed.
例如,要添加仅应针对具有调试信息的配置执行的测试,可以列出Debug和配置。RelWithDebInfo添加空字符串还可以在运行测试时未指定配置的情况下运行测试:
For example, to add a test that should only be executed for configurations that
have debug information, the Debug and RelWithDebInfo configurations can be
listed. Adding the empty string also makes the test run when no configuration
is specified when running the tests:
add_test(NAME DebugOnly
COMMAND TestApp
CONFIGURATIONS Debug RelWithDebInfo ""
)add_test(NAME DebugOnly
COMMAND TestApp
CONFIGURATIONS Debug RelWithDebInfo ""
)
在大多数情况下,CONFIGURATIONS不需要该关键字,并且将对所有配置(包括空配置)执行测试。
In most cases, the CONFIGURATIONS keyword is not needed and the test would be
executed for all configurations, including the empty one.
默认情况下,测试将在该CMAKE_CURRENT_BINARY_DIR目录中运行,但该WORKING_DIRECTORY选项可用于使测试在其他位置运行。一个有用的例子是在不同的目录中运行相同的可执行文件来获取不同的输入文件集,而不必将它们指定为命令行参数。
By default, the test will run in the CMAKE_CURRENT_BINARY_DIR directory, but
the WORKING_DIRECTORY option can be used to make the test run in some other
location. An example of where this can be useful is to run the same executable
in different directories to pick up different sets of input files without
having to specify them as command line arguments.
add_test(NAME Foo
COMMAND TestApp
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Foo
)
add_test(NAME Bar
COMMAND TestApp
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Bar
)add_test(NAME Foo
COMMAND TestApp
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Foo
)
add_test(NAME Bar
COMMAND TestApp
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Bar
)
如果指定工作目录,请始终使用绝对路径。如果给出相对路径,它将被解释为相对于ctest启动自身的目录,但这可能不是构建树的顶部。为了确保工作目录是可预测的,项目应避免使用相对的WORKING_DIRECTORY.
If specifying a working directory, always use an absolute path. If a relative
path is given, it will be interpreted as being relative to the directory in
which ctest itself was launched, but that might not be the top of the build
tree. In order to ensure the working directory is predictable, projects should
avoid using a relative WORKING_DIRECTORY.
如果运行测试时指定的工作目录不存在,CMake 3.11 及更早版本将不会发出错误消息,并且仍会运行测试,即使它无法更改工作目录。CMake 3.12 及更高版本将捕获错误并将测试视为失败。无论使用哪个版本的 CMake,项目都有责任确保工作目录存在并具有适当的权限。
If the specified working directory does not exist when the test is run, CMake versions 3.11 and earlier will not issue an error message and will still run the test, even though it fails to change the working directory. CMake 3.12 and later will catch the error and treat the test as failed. Regardless of what version of CMake is being used, it is the project’s responsibility to ensure the working directory exists and has appropriate permissions.
CMake 3.16 添加了对关键字的支持,该关键字与和
命令COMMAND_EXPAND_LISTS的同名选项具有相同的效果。当存在此关键字时,将扩展作为命令名称或参数给出的任何列表。此列表扩展发生在计算任何生成器表达式之后。此功能的主要动机之一是避免在生成器表达式扩展为空后将不需要的空字符串作为命令参数传递。例如:add_custom_command()add_custom_target()
CMake 3.16 added support for the COMMAND_EXPAND_LISTS keyword, which has the
same effect as the same-named option for the add_custom_command() and
add_custom_target() commands.
When this keyword is present, any list given as a command name or argument
is expanded.
This list expansion occurs after any generator expressions are evaluated.
One of the primary motivations for this feature is to avoid passing an unwanted
empty string as a command argument after a generator expression expands to
nothing.
For example:
add_test(NAME Expander
COMMAND someCommand $<$<CONFIG:Debug>:-g>
COMMAND_EXPAND_LISTS
)add_test(NAME Expander
COMMAND someCommand $<$<CONFIG:Debug>:-g>
COMMAND_EXPAND_LISTS
)
如果针对非调试配置运行上述测试,COMMAND_EXPAND_LISTS
请确保在生成器表达式扩展为空后,不会将空参数添加到命令行中。
If the above test is run for non-Debug configurations, COMMAND_EXPAND_LISTS
ensures that after the generator expression expands to nothing, no empty
argument is added to the command line.
add_test()出于向后兼容性的原因,还支持该命令的简化形式:
A reduced form of the add_test() command is also supported for backward
compatibility reasons:
add_test(testName command [args...])add_test(testName command [args...])
NAME这种形式不应该在新项目中使用,因为它缺乏完整形式的一些功能COMMAND。主要区别在于不支持生成器表达式,并且如果command是目标的名称,CMake 将不会自动替换其二进制文件的位置。
This form should not be used in new projects, since it lacks some of the
features of the full NAME and COMMAND form. The main differences are that
generator expressions are not supported and if command is the name of a
target, CMake will not automatically substitute the location of its binary.
要运行测试,ctest需要使用命令行工具。它通常从构建目录的顶部运行,尽管使用 CMake 3.20 或更高版本,--test-dir可以给出命令行选项来指定应运行测试的目录。默认情况下,ctest将一次执行一个定义的所有测试,在每个测试开始和完成时记录一条状态消息,但隐藏所有测试输出。最后将打印测试的总体摘要。典型的输出如下所示:
To run the tests, the ctest command line tool is used.
It would normally be run from the top of the build directory, although with
CMake 3.20 or later, a --test-dir command line option can be given to
specify the directory for which tests should be run.
By default, ctest will execute all defined tests one at a time, logging a
status message as each test is started and completed, but hiding all test
output.
An overall summary of the tests will be printed at the end.
Typical output would look something like this:
测试项目 /path/to/build/dir
开始 1:FooWithBar
1/2 测试#1:FooWithBar............通过了 0.00 秒
开始 2:FooWithoutBar
2/2 测试#2:FooWithoutBar...........通过了 0.00 秒
100% 测试通过,2 次测试中有 0 次失败
总测试时间(实际)= 0.02 秒Test project /path/to/build/dir
Start 1: FooWithBar
1/2 Test #1: FooWithBar.............. Passed 0.00 sec
Start 2: FooWithoutBar
2/2 Test #2: FooWithoutBar........... Passed 0.00 sec
100% tests passed, 0 tests failed out of 2
Total Test time (real) = 0.02 sec如果使用 Xcode、Visual Studio 或 Ninja Multi-Config 等多配置生成器,ctest则需要告知要测试哪个配置。这是通过包含-C configType选项 where configTypewill be 支持的构建类型之一(Debug、Release等)来完成的。对于单个配置生成器,该-C选项不是强制性的,因为构建只能生成一种配置,因此在哪里找到要执行的二进制文件没有歧义。尽管如此,指定一个配置仍然很有用,以避免排除那些定义为仅在某些配置下运行且空字符串不在列出的配置中的测试的不太直观的行为。
If using a multi configuration generator like Xcode, Visual Studio or Ninja
Multi-Config, ctest needs to be told which configuration to test.
This is done by including the -C configType option where configType will be
one of the supported build types (Debug, Release, etc.).
For single configuration generators, the -C option is not mandatory, since
the build can only produce one configuration, so there is no ambiguity for
where to find the binaries to execute.
Nevertheless, it can still be useful to specify a configuration to avoid the
less intuitive behavior of excluding tests that are defined to only run under
certain configurations and where the empty string is not among those listed.
ctest可以指示显示所有测试输出以及有关使用该-V选项运行的各种其他详细信息。
-VV显示了更多的冗长程度,但这通常只有开发人员需要自行处理ctest。即使-V详细程度通常比用户希望看到的更详细,但更有可能的是,只有失败的测试的输出才有意义。
ctest通过传递该选项,可以被告知仅显示失败测试的输出
--output-on-failure。或者,开发人员可以设置CTEST_OUTPUT_ON_FAILURE环境变量,以避免每次都指定它(不使用该值,
ctest仅检查是否CTEST_OUTPUT_ON_FAILURE已设置)。使用 CMake 3.18 或更高版本,--stop-on-failure还可以提供在遇到第一个错误时立即结束测试运行的选项。
ctest can be instructed to show all test output and various other details
about the run with the -V option.
-VV shows an increased level of verbosity, but this is typically only needed
by developers working on ctest itself.
Even the -V level of verbosity is usually more detail than users want to see,
it is more likely that only the output of tests that fail are of interest.
ctest can be told to only show the output of failed tests by passing the
--output-on-failure option.
Alternatively, developers can set the CTEST_OUTPUT_ON_FAILURE environment
variable to avoid having to specify it every time (the value isn’t used,
ctest merely checks if CTEST_OUTPUT_ON_FAILURE has been set).
With CMake 3.18 or later, the --stop-on-failure option can also be given to
end the test run immediately upon the first error encountered.
默认情况下,每个测试将在与命令相同的环境下运行ctest
。如果测试需要更改其环境,可以通过ENVIRONMENT测试属性来完成。该属性应该是NAME=VALUE定义运行测试之前要设置的环境变量的项目列表。更改仅适用于该测试,不会影响其他测试。
By default, each test will be run with the same environment as the ctest
command. If a test requires changes to its environment, this can be done
through the ENVIRONMENT test property. This property is expected to be a
list of NAME=VALUE items that define environment variables to be set before
running the test. Changes are local to that test only and do not affect other
tests.
set_tests_properties(FooWithoutBar PROPERTIES
ENVIRONMENT "FOO=bar;HAVE_BAZ=1"
)set_tests_properties(FooWithoutBar PROPERTIES
ENVIRONMENT "FOO=bar;HAVE_BAZ=1"
)
环境变量需要修改而不是替换现有值的情况就不那么简单了。如果环境应基于运行 CMake 的环境而不是命令,则可以使用ctest该表单
来获取现有值。$ENV{SOMEVAR}一个很好的例子是增加PATH环境变量以确保测试可以找到它在 Windows 上链接的共享库:
Situations where an environment variable needs to modify rather than replace an
existing value are less straightforward. If the environment should be based on
the one in which CMake is run rather than the ctest command, then the form
$ENV{SOMEVAR} can be used to obtain existing values. A good example of this
is when augmenting the PATH environment variable to ensure a test can find
the shared libraries it links against on Windows:
# In this example, Algo is assumed to be a shared library
# defined elsewhere in the project and whose binary will
# be in a different directory to test_Foo
add_executable(test_Foo ...)
target_link_libraries(test_Foo PRIVATE Algo)
add_test(NAME FooWithAlgo COMMAND test_Foo)
if(WIN32)
set(algoDir "$<SHELL_PATH:$<TARGET_FILE_DIR:Algo>>")
set(execPath "PATH=${algoDir}$<SEMICOLON>$ENV{PATH}")
set_tests_properties(FooWithAlgo PROPERTIES
ENVIRONMENT "${execPath}"
)
endif()# In this example, Algo is assumed to be a shared library
# defined elsewhere in the project and whose binary will
# be in a different directory to test_Foo
add_executable(test_Foo ...)
target_link_libraries(test_Foo PRIVATE Algo)
add_test(NAME FooWithAlgo COMMAND test_Foo)
if(WIN32)
set(algoDir "$<SHELL_PATH:$<TARGET_FILE_DIR:Algo>>")
set(execPath "PATH=${algoDir}$<SEMICOLON>$ENV{PATH}")
set_tests_properties(FooWithAlgo PROPERTIES
ENVIRONMENT "${execPath}"
)
endif()
根据用于调用的实际环境
ctest而不是 CMake 来修改环境更加复杂,并且通常不是严格要求的。cmake -E env它可以通过调用脚本
的组合来实现,将 CMake 提供的位置作为变量传递给cmake -E env部件,然后脚本执行使用这些值增强运行时环境并调用测试可执行文件的实际任务。这种布置很复杂,可能很脆弱,应该避免,除非确实需要支持这种用例。
Modifying the environment based on the actual environment being used to invoke
ctest rather than CMake is more involved and is usually not strictly
required. It can be achieved with a combination of cmake -E env invoking a
script, with CMake-provided locations being passed as variables to the
cmake -E env part, then the script does the actual task of augmenting the
run-time environment using those values and invoking the test executable.
Such an arrangement is complex, can be fragile and should be avoided unless
there is a definite need to support such a use case.
主要为了方便 IDE 应用程序,启用测试后,CMake 定义一个自定义构建目标,该目标ctest使用一组默认参数进行调用。对于 Xcode 和 Visual Studio 生成器,将调用此目标RUN_TESTS,并将当前选择的构建类型作为配置传递给ctest. 对于其他生成器,只需调用目标test,如果它是单个配置生成器,则该目标在调用时不会指定任何配置ctest。
As a convenience primarily for IDE applications, when testing has been enabled,
CMake defines a custom build target that invokes ctest with a default set of
arguments. For the Xcode and Visual Studio generators,
this target will be called RUN_TESTS and it will pass the currently selected
build type as the configuration to ctest. For other generators, the target is
simply called test and if it is a single configuration generator, that target
does not specify any configuration when invoking ctest.
ctest对于 CMake 3.16 或更早版本,在使用
RUN_TESTS或构建目标时,无法指定将执行哪些测试或要传递到的任何其他自定义选项test。CMake 3.17 引入了该CMAKE_CTEST_ARGUMENTS变量,可用于在ctest该构建目标的命令行中添加任意选项。
With CMake 3.16 or earlier, there is no facility to specify which tests will be
executed or any other custom options to pass to ctest when using the
RUN_TESTS or test build target.
CMake 3.17 introduced the CMAKE_CTEST_ARGUMENTS variable, which can be
used to prepend arbitrary options to the ctest command line for that build
target.
纯粹基于测试命令的退出代码来得出测试结果可能会受到很大限制。另一种受支持的替代方法是指定正则表达式来与测试输出进行匹配。testPASS_REGULAR_EXPRESSION
属性可用于指定正则表达式列表,测试输出必须至少匹配其中一个正则表达式才能通过测试。这些正则表达式经常跨越多行。类似地,FAIL_REGULAR_EXPRESSION测试属性可以设置为正则表达式列表。如果其中任何一个与测试输出匹配,则测试将失败,即使输出也匹配 aPASS_REGULAR_EXPRESSION或退出代码为 0。测试可以同时具有PASS_REGULAR_EXPRESSION和FAIL_REGULAR_EXPRESSION
set,仅两者之一或两者都不具有。如果PASS_REGULAR_EXPRESSION设置且不为空,则在确定测试是否通过时不考虑退出代码。
Basing the result of a test purely on the exit code of the test command can be
quite restrictive. Another supported alternative is to specify regular
expressions to match against the test output. The PASS_REGULAR_EXPRESSION
test property can be used to specify a list of regular expressions, at least
one of which the test output must match for the test to pass. These regular
expressions frequently span across multiple lines.
Similarly, the FAIL_REGULAR_EXPRESSION test property can be set to a list
of regular expressions. If any of these match the test output, the test fails,
even if the output also matches a PASS_REGULAR_EXPRESSION or the exit code is
0. A test can have both PASS_REGULAR_EXPRESSION and FAIL_REGULAR_EXPRESSION
set, just one of the two or neither. If PASS_REGULAR_EXPRESSION is set and is
not empty, the exit code is not considered when determining whether the test
passes or fails.
# Ignore exit code, check output to determine the
# pass/fail status
set_tests_properties(test_Foo PROPERTIES
FAIL_REGULAR_EXPRESSION "warning|Warning|WARNING"
PASS_REGULAR_EXPRESSION [[
Checking some condition for test_Foo: passed
+.*
All checks passed]]
)# Ignore exit code, check output to determine the
# pass/fail status
set_tests_properties(test_Foo PROPERTIES
FAIL_REGULAR_EXPRESSION "warning|Warning|WARNING"
PASS_REGULAR_EXPRESSION [[
Checking some condition for test_Foo: passed
+.*
All checks passed]]
)
有时可能需要跳过测试,也许出于只有测试本身才能确定的原因。测试SKIP_RETURN_CODE属性可以设置为测试可以返回的值,以指示它被跳过而不是失败。以 退出的测试SKIP_RETURN_CODE将覆盖任何其他通过/失败标准。
Sometimes a test may need to be skipped, perhaps for reasons that only the test
itself can determine. The SKIP_RETURN_CODE test property can be set to a
value the test can return to indicate that it was skipped rather than failed. A
test that exits with the SKIP_RETURN_CODE will override any other pass/fail
criteria.
add_executable(test_Foo test_Foo.cpp ...)
add_test(NAME Foo COMMAND test_Foo)
set_tests_properties(Foo PROPERTIES
SKIP_RETURN_CODE 2
)add_executable(test_Foo test_Foo.cpp ...)
add_test(NAME Foo COMMAND test_Foo)
set_tests_properties(Foo PROPERTIES
SKIP_RETURN_CODE 2
)
int main(int argc, char* argv[])
{
if (shouldSkip())
return 2; // Skipped
if (runTest())
return 0; // Passed
return 1; // Failed
}int main(int argc, char* argv[])
{
if (shouldSkip())
return 2; // Skipped
if (runTest())
return 0; // Passed
return 1; // Failed
}
上述测试的输出可能类似于以下内容:
Output from the above test may look similar to the following:
测试项目 /path/to/build/dir
开始 1:福
1/1 测试 #1:Foo ...................***跳过 0.00 秒
100% 测试通过,1 次测试中有 0 次失败
总测试时间(实际)= 0.01 秒
以下测试未运行:
1 - Foo(已跳过)Test project /path/to/build/dir
Start 1: Foo
1/1 Test #1: Foo ....................***Skipped 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.01 sec
The following tests did not run:
1 - Foo (Skipped)从 CMake 3.16 开始,SKIP_REGULAR_EXPRESSION还可以指定 a。PASS_REGULAR_EXPRESSION这与和 的
工作方式完全相同FAIL_REGULAR_EXPRESSION,如果输出与任何跳过表达式匹配,则导致测试被跳过。跳过正则表达式也优先于任何通过或失败标准。
From CMake 3.16, a SKIP_REGULAR_EXPRESSION can also be specified.
This works in exactly the same way as PASS_REGULAR_EXPRESSION and
FAIL_REGULAR_EXPRESSION, causing the test to be skipped if the output
matches any of the skip expressions.
A skip regular expression also takes precedence over any pass or fail criteria.
当至少一个测试失败或由于某种原因未运行时,所有此类测试及其状态的摘要将在最后打印。通过返回代码指示应跳过的测试仍计入测试总数。这些跳过的测试在 CMake 3.9 或更高版本中不被视为失败,但在 CMake 3.8 及更早版本中被视为失败。无论 CMake 版本如何,测试也可能因其他可能被视为失败的原因而被跳过,例如未能满足测试依赖性(在下面的第 25.6 节“测试依赖性”中讨论)。
When at least one test fails or is not run for some reason, a summary of all such tests and their status is printed at the end. A test that indicates it should be skipped via its return code is still counted in the total number of tests. These skipped tests are not considered failures with CMake 3.9 or later, but they are considered failures with CMake 3.8 and earlier. Regardless of CMake version, a test may also be skipped for other reasons which could be deemed a failure, such as a test dependency failing to be met (discussed in Section 25.6, “Test Dependencies” below).
使用 CMake 3.9 或更高版本,DISABLED还支持测试属性。这可用于将测试标记为暂时禁用,这将允许定义该测试,但不执行该测试,甚至不将其计入测试总数。它不会被视为测试失败,但仍会在测试结果中显示并带有适当的状态消息。请注意,此类测试通常不应长时间保持禁用状态,该功能旨在作为禁用有问题或不完整的测试直到其得到修复的临时方法。以下简单示例演示了DISABLED测试行为:
With CMake 3.9 or later, a DISABLED test property is also supported. This
can be used to mark a test as temporarily disabled, which will allow it to be
defined, but not executed or even counted in the total number of tests. It will
not be considered a test failure, but it will still be shown in the test
results with an appropriate status message. Note that such tests should not
normally remain disabled for extended periods, the feature is intended as a
temporary way to disable a problematic or incomplete test until it can be
fixed. The following simple example demonstrates the DISABLED test behavior:
add_test(NAME FooWithBar ...)
add_test(NAME FooWithoutBar ...)
set_tests_properties(FooWithoutBar PROPERTIES DISABLED YES)add_test(NAME FooWithBar ...)
add_test(NAME FooWithoutBar ...)
set_tests_properties(FooWithoutBar PROPERTIES DISABLED YES)
ctest上面的输出可能看起来像这样:
The ctest output for the above may look something like this:
测试项目 /path/to/build/dir
开始 1:FooWithBar
1/2 测试#1:FooWithBar ......通过了 0.00 秒
开始 2:FooWithoutBar
2/2 测试#2:FooWithoutBar ....***未运行(禁用)0.00 秒
100% 测试通过,1 次测试中有 0 次失败
总测试时间(实际)= 0.01 秒
以下测试未运行:
2 - FooWithoutBar(已禁用)Test project /path/to/build/dir
Start 1: FooWithBar
1/2 Test #1: FooWithBar ....... Passed 0.00 sec
Start 2: FooWithoutBar
2/2 Test #2: FooWithoutBar ....***Not Run (Disabled) 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.01 sec
The following tests did not run:
2 - FooWithoutBar (Disabled)在某些情况下,测试可能会失败。与禁用测试相比,将测试标记为预期失败以便继续执行可能更合适。可以将测试WILL_FAIL属性设置为 true 来指示这一点,然后这将反转通过/失败结果。这样做的另一个优点是,如果测试开始意外通过,ctest将认为失败,并且开发人员会立即意识到行为的变化。
In some cases, a test may be expected to fail. Rather than disabling the test,
it may be more appropriate to mark the test as expecting failure so that it
continues to be executed. The WILL_FAIL test property can be set to true
to indicate this, which will then invert the pass/fail result. This has the
added advantage that if the test starts to pass unexpectedly, ctest will
consider that a failure and the developer is immediately aware of the change in
behavior.
测试通过/失败状态的另一个方面是完成测试所需的时间。如果设置了测试TIMEOUT属性,则指定测试在终止并标记为失败之前允许运行的秒数。命令
ctest行还接受一个--timeout选项,该选项对于没有设置属性的任何测试具有相同的效果TIMEOUT(即,它充当默认超时)。--stop-time此外,还可以通过指定选项将时间限制应用于整个测试集ctest。之后的参数--stop-time必须是一天中的实时时间而不是秒数,如果未给出时区,则假定为当地时间。
Another aspect of a test’s pass/fail status is how long it takes to complete.
The TIMEOUT test property, if set, specifies the number of seconds the
test is allowed to run before it will be terminated and marked as failed. The
ctest command line also accepts a --timeout option which has the same
effect for any test without a TIMEOUT property set (i.e. it acts as a default
timeout). Furthermore, a time limit can also be applied to the entire set of
tests as a whole by specifying the --stop-time option to ctest. The
argument after --stop-time must be a real time of day rather than a number of
seconds, with local time assumed if no timezone is given.
add_test(NAME t1 COMMAND ...)
add_test(NAME t2 COMMAND ...)
set_tests_properties(t2 PROPERTIES TIMEOUT 10)add_test(NAME t1 COMMAND ...)
add_test(NAME t2 COMMAND ...)
set_tests_properties(t2 PROPERTIES TIMEOUT 10)
ctest --超时 30 --停止时间 13:00
ctest --timeout 30 --stop-time 13:00
在上面的示例中,命令行中的默认每次测试超时设置为 30 秒
ctest。由于t1没有TIMEOUT设置属性,因此它将有 30 秒的超时,而t2其TIMEOUT属性设置为 10,这将覆盖命令行上的默认设置ctest。测试将在当地时间下午 1 点之前完成。
In the above example, the default per-test timeout is set to 30 seconds on the
ctest command line. Since t1 has no TIMEOUT property set, it will have a
30 second timeout, whereas t2 has its TIMEOUT property set to 10, which
will override the default set on the ctest command line. The tests will be
given until 1pm local time to complete.
在某些情况下,测试可能需要等待特定条件才能正确开始测试。在满足该条件并且真正的测试开始之后,可能需要对运行的部分应用超时。对于 CMake 3.6 或更高版本,TIMEOUT_AFTER_MATCH测试属性可用于支持此行为。它需要一个包含两项的列表,第一个是满足条件后用作超时的秒数,第二个是与测试输出匹配的正则表达式。找到正则表达式后,测试的超时倒计时和开始时间将重置,并将超时值设置为第一个列表项。
In some circumstances, a test may need to wait for a particular condition
before it starts the test proper. It may be desirable to apply a timeout to
just the part of the run after that condition has been met and the real test
begins. With CMake 3.6 or later, the TIMEOUT_AFTER_MATCH test property is
available to support this behavior. It expects a list containing two items, the
first being the number of seconds to be used as a timeout after the condition
is met and the second is a regular expression to be matched against the test
output. When the regular expression is found, the test’s timeout countdown and
start time is reset and the timeout value is set to the first list item.
例如,以下代码将对测试应用 30 秒的总体超时,但是一旦该字符串Condition met出现在测试输出中,测试将有 10 秒的时间从该点开始完成,并且原始 30 秒的超时条件将不再适用:
For example, the following will apply an overall timeout of 30 seconds to the
test, but once the string Condition met appears in the test output, the test
will have 10 seconds to complete from that point and the original 30 second
timeout condition will no longer apply:
set_tests_properties(t2 PROPERTIES
TIMEOUT 30
TIMEOUT_AFTER_MATCH "10;Condition met"
)set_tests_properties(t2 PROPERTIES
TIMEOUT 30
TIMEOUT_AFTER_MATCH "10;Condition met"
)
如果测试需要 25 秒才能满足条件,则测试的总时间可能会长达 35 秒,但由于测试的开始时间也被重置,因此ctest会报告 0 到 10 秒之间的时间(即时间满足的条件不计算在内)。另一方面,如果在 30 秒内未能满足条件,则测试将显示大约 30 秒的总测试时间。
If the test took 25 seconds for the condition to be satisfied, the overall time
of the test could be as long as 35 seconds, but because the test’s start time
is also reset, ctest would report a time between 0 and 10 seconds (i.e. the
time for the condition to be met is not counted). If, on the other hand, the
condition fails to be met within 30 seconds, the test will show an overall test
time of about 30 seconds.
上述报告的时间可能有些令人困惑。因此,在可能的情况下,通常应避免使用 ,TIMEOUT_AFTER_MATCH而应采用其他方式来处理先决条件。
下面的第 25.6 节“测试依赖性”和第 25.4 节“并行执行”讨论了更好的替代方法。
The reported times for the above can be somewhat confusing.
Therefore, where possible, the use of TIMEOUT_AFTER_MATCH should generally be
avoided in favor of other ways to handle preconditions.
Section 25.6, “Test Dependencies” and Section 25.4, “Parallel Execution” further below discuss better
alternative methods.
在较大的项目中,想要运行所有已定义测试的子集是很常见的。开发人员在处理该问题时可能会专注于特定的失败测试,并且可能对所有其他测试不感兴趣。CMake 提供了几种不同的方法来缩小测试运行中包含的测试集范围。
In larger projects, it is quite common to want to run just a subset of all defined tests. The developer may be focusing on a particular failing test and may not be interested in all the other tests while working on that problem. CMake offers a few different ways to narrow down the set of tests to include in a test run.
仅执行测试的特定子集的一种方法是向 提供-R和
-E选项ctest。这些选项各自指定要与测试名称匹配的正则表达式。该-R选项选择要包含在测试集中的测试,反之则-E排除测试。可以指定这两个选项以组合它们的效果。
One way to execute just a specific subset of tests is by giving the -R and
-E options to ctest.
These options each specify a regular expression to be matched against test
names. The -R option selects tests to be included in the test set,
whereas -E excludes tests.
Both options can be specified to combine their effects.
add_test(NAME FooOnly COMMAND ...)
add_test(NAME BarOnly COMMAND ...)
add_test(NAME FooWithBar COMMAND ...)
add_test(NAME FooSpecial COMMAND ...)
add_test(NAME Other_Foo COMMAND ...)add_test(NAME FooOnly COMMAND ...)
add_test(NAME BarOnly COMMAND ...)
add_test(NAME FooWithBar COMMAND ...)
add_test(NAME FooSpecial COMMAND ...)
add_test(NAME Other_Foo COMMAND ...)
# 只运行 FooOnly 和 BarOnly 仅 ctest -R # 运行除 FooWithBar 之外的所有内容 ctest -E 栏 # 运行除 FooSpecial 之外以 Foo 开头的所有测试 ctest -R '^Foo' -E FooSpecial # 仅运行 FooSpecial 和 Other_Foo ctest -R 'FooSpecial|Other_Foo'
# Run just FooOnly and BarOnly ctest -R Only # Run all but FooWithBar ctest -E Bar # Run all tests starting with Foo except FooSpecial ctest -R '^Foo' -E FooSpecial # Run only FooSpecial and Other_Foo ctest -R 'FooSpecial|Other_Foo'
有时,制定正则表达式来捕获所需的测试并不总是那么容易,或者开发人员可能只想查看已定义的所有测试而不运行它们。该-N选项指示ctest仅打印测试而不是运行它们,这可以是检查正则表达式是否生成所需测试集的有用方法。
Sometimes it isn’t always easy to work out a regular expression to capture just
the desired tests, or a developer may just want to see all the tests that have
been defined without running them. The -N option instructs ctest to only
print the tests rather than run them, which can be a useful way to check that
the regular expressions yield the desired set of tests.
测试-N 测试项目 /path/to/build/dir 测试#1:FooOnly 测试 #2:仅 Bar 测试#3:FooWithBar 测试#4:FooSpecial 测试#5:Other_Foo 总测试:5
ctest -N Test project /path/to/build/dir Test #1: FooOnly Test #2: BarOnly Test #3: FooWithBar Test #4: FooSpecial Test #5: Other_Foo Total Tests: 5
ctest -N -R 'FooSpecial|Other_Foo' 测试项目 /path/to/build/dir 测试#4:FooSpecial 测试#5:Other_Foo 总测试:2
ctest -N -R 'FooSpecial|Other_Foo' Test project /path/to/build/dir Test #4: FooSpecial Test #5: Other_Foo Total Tests: 2
添加每个测试时,都会给出一个测试编号。该数字在两次运行之间将保持不变,除非在项目中的该数字之前添加或删除了另一个测试。输出ctest在测试旁边显示该数字。使用该-N选项时,测试按项目定义的顺序列出,但测试可能不一定按该顺序执行。可以使用该选项通过测试编号而不是名称来选择要运行的测试-I
。这种方法相当脆弱,因为添加或删除单个测试可能会更改分配给任意数量的其他测试的编号。-C即使通过选项传递不同的配置也ctest可能导致测试编号发生变化。在大多数情况下,按名称匹配会更好。
As each test is added, it is given a test number.
This number will remain the same between runs unless another test is added or
removed before it in the project.
The ctest output shows this number beside the test.
When using the -N option, tests are listed in the order they have been
defined by the project, but the tests might not necessarily be executed in that
order.
Tests to be run can be selected by test number rather than name using the -I
option.
This method is rather fragile, since the addition or removal of a single test
can change the number assigned to any number of other tests.
Even passing a different configuration via the -C option to ctest can
result in the test numbers changing.
In most cases, matching by name will be preferable.
测试编号有用的一种情况是两个测试的名称完全相同。除非在同一目录中定义,否则这两个测试都会被接受,并且不会发出任何警告。虽然通常应避免重复的测试名称,但在涉及外部提供的测试的分层项目中,这可能并不总是可行。
One situation where test numbers can be useful is where two tests have been given exactly the same name. Except when defined in the same directory, both tests are accepted without any warnings being issued. While duplicate test names should generally be avoided, in hierarchical projects involving externally provided tests, this may not always be possible.
该-I选项需要一个形式有些复杂的参数。最直接的形式包括在命令行上指定测试编号,用逗号分隔,不带空格:
The -I option expects an argument which has a somewhat complicated form. The
most direct form involves specifying test numbers on the command line,
separated by commas with no spaces:
ctest -I [开始[,结束[,步幅[,testNum[,testNum...]]]]]
ctest -I [start[,end[,stride[,testNum[,testNum...]]]]]
要仅指定单独的测试编号,start,end和stride可以留空,如下所示:
To specify just individual test numbers, the start, end and stride can be
left blank like so:
ctest -I ,,,3,2 # 仅选择测试 2 和 3
ctest -I ,,,3,2 # Selects tests 2 and 3 only
可以从文件中读取相同的详细信息,而不是通过向选项提供文件名来在命令行上指定-I。如果定期运行相同的复杂测试集并且不添加或删除任何测试,这可能会很有用:
The same details can be read from a file instead of being specified on the
command line by giving the name of the file to the -I option. This can be
useful if regularly running the same complicated set of tests and no tests are
being added or removed:
,,,3,2,,,3,2
ctest -I testNumbers.txt
ctest -I testNumbers.txt
如果需要执行大量相关测试,则按名称或编号单独选择测试可能会变得很麻烦。可以使用 test 属性为测试分配任意标签列表LABELS,然后可以通过这些标签选择测试。和选项分别类似于-L和选项,只不过
它们作用于测试标签而不是测试名称。继续使用前面示例中定义的相同测试:-LE-R-E
Selecting tests individually by name or number can become cumbersome if a large
set of related tests needs to be executed. Tests can be assigned an arbitrary
list of labels using the LABELS test property and then tests can be
selected by these labels. The -L and -LE options are analogous to the -R
and -E options respectively, except they operate on test labels rather than
test names. Continuing with the same tests defined in the earlier example:
set_tests_properties(FooOnly PROPERTIES
LABELS "Foo"
)
set_tests_properties(BarOnly PROPERTIES
LABELS "Bar"
)
set_tests_properties(FooWithBar PROPERTIES
LABELS "Foo;Bar;Multi"
)
set_tests_properties(FooSpecial PROPERTIES
LABELS "Foo"
)
set_tests_properties(Other_Foo PROPERTIES
LABELS "Foo"
)set_tests_properties(FooOnly PROPERTIES
LABELS "Foo"
)
set_tests_properties(BarOnly PROPERTIES
LABELS "Bar"
)
set_tests_properties(FooWithBar PROPERTIES
LABELS "Foo;Bar;Multi"
)
set_tests_properties(FooSpecial PROPERTIES
LABELS "Foo"
)
set_tests_properties(Other_Foo PROPERTIES
LABELS "Foo"
)
ctest -L 条
测试项目 /path/to/build/dir
开始 2:仅酒吧
1/2 测试#2:BarOnly .................通过了 1.52 秒
开始 3:FooWithBar
2/2 测试 #3:FooWithBar ......................通过了 1.02 秒
100% 测试通过,2 次测试中有 0 次失败
标签时间摘要:
Bar = 2.53 秒*proc(2 次测试)
Foo = 1.02 秒*proc(1 次测试)
多次 = 1.02 秒*过程(1 次测试)
总测试时间(实际)= 2.54 秒ctest -L Bar
Test project /path/to/build/dir
Start 2: BarOnly
1/2 Test #2: BarOnly ................. Passed 1.52 sec
Start 3: FooWithBar
2/2 Test #3: FooWithBar .............. Passed 1.02 sec
100% tests passed, 0 tests failed out of 2
Label Time Summary:
Bar = 2.53 sec*proc (2 tests)
Foo = 1.02 sec*proc (1 test)
Multi = 1.02 sec*proc (1 test)
Total Test time (real) = 2.54 sec标签不仅可以方便地对测试执行进行分组,还可以对基本的执行时间统计进行分组。如上面的示例输出所示,ctest当已执行测试集中的任何测试LABELS设置了其属性时,该命令将打印标签摘要。这使开发人员能够了解每个标签组对整体测试时间的贡献。proc单元部分是sec*proc指分配给测试的处理器数量(在下面的第 25.4 节“并行执行”中描述)。运行 3 秒且需要 4 个处理器的测试将报告值 12。可以使用该--no-label-summary
选项抑制标签时间摘要。
Labels not only enable convenient grouping for test execution, they also
provide grouping for basic execution time statistics. As seen in the above
example output, the ctest command prints a label summary when any tests in
the set of executed tests has its LABELS property set. This allows the
developer to get an idea how each label group is contributing to the overall
test time. The proc part of the sec*proc units refers to the number of
processors allocated to tests (described in Section 25.4, “Parallel Execution” below). A
test that ran for 3 seconds and required 4 processors would report a value of
12. The label time summary can be suppressed with the --no-label-summary
option.
另一个常见的需求是重新运行上次
ctest运行失败的测试。这是在进行小修复后重新检查相关测试或重新运行由于某些临时环境条件而失败的测试的便捷方法。该ctest命令支持一个
--rerun-failed选项,该选项提供此行为,而无需给出任何测试名称、数字或标签。
Another common need is to re-run just those tests that failed the last time
ctest was run. This can be a convenient way to re-check just the relevant
tests after making a small fix or to re-run tests that failed due to some
temporary environmental condition. The ctest command supports a
--rerun-failed option which provides this behavior without needing any test
names, numbers or labels to be given.
有时,特定测试或一组测试只会间歇性失败,因此可能需要运行多次测试才能尝试重现故障。该选项可以给出每个测试可以重复的次数上限,而不是ctest一遍又一遍地运行。--repeat-until-fail如果测试失败,则不会针对该ctest调用再次重新运行。
Sometimes a particular test or set of tests only fails intermittently, so the
test(s) may need to be run many times to try to reproduce a failure. Rather
than running ctest itself over and over, the --repeat-until-fail option can
be given with the upper limit on the number of times each test can be repeated.
If a test fails, it will not be re-run again for that ctest invocation.
ctest -L Bar --重复直到失败 3
测试项目 /path/to/build/dir
开始 2:仅酒吧
测试#2:BarOnly .................通过了 1.52 秒
开始 2:仅酒吧
测试 #2:仅 Bar......................***失败 0.00 秒
开始 3:FooWithBar
测试 #3:FooWithBar ......................通过了 1.02 秒
开始 3:FooWithBar
测试 #3:FooWithBar ......................通过了 1.02 秒
开始 3:FooWithBar
2/2 测试 #3:FooWithBar ......................通过了 1.02 秒
50% 测试通过,2 项测试中有 1 项失败
标签时间摘要:
Bar = 1.02 秒*proc(2 次测试)
Foo = 1.02 秒*proc(1 次测试)
多次 = 1.02 秒*过程(1 次测试)
总测试时间(实际)= 4.59 秒
以下测试失败:
2 - 仅酒吧(失败)
运行 CTest 时出错ctest -L Bar --repeat-until-fail 3
Test project /path/to/build/dir
Start 2: BarOnly
Test #2: BarOnly ................. Passed 1.52 sec
Start 2: BarOnly
Test #2: BarOnly .................***Failed 0.00 sec
Start 3: FooWithBar
Test #3: FooWithBar .............. Passed 1.02 sec
Start 3: FooWithBar
Test #3: FooWithBar .............. Passed 1.02 sec
Start 3: FooWithBar
2/2 Test #3: FooWithBar .............. Passed 1.02 sec
50% tests passed, 1 tests failed out of 2
Label Time Summary:
Bar = 1.02 sec*proc (2 tests)
Foo = 1.02 sec*proc (1 test)
Multi = 1.02 sec*proc (1 test)
Total Test time (real) = 4.59 sec
The following tests FAILED:
2 - BarOnly (Failed)
Errors while running CTest标签摘要不会累积重复测试的总时间,它仅使用测试上次执行的时间。然而,总测试时间会计算所有重复次数。
The label summary doesn’t accumulate the total time for the repeated tests, it only uses the time of a test’s last execution. The total test time does, however, count all repeats.
CMake 3.17 扩展了重复功能,可以重新运行涵盖更多情况的测试。添加了一个新ctest选项,其中是运行测试的最大次数,并且是以下之一:--repeat mode:nnmode
CMake 3.17 expanded the repeat capabilities to re-run tests covering more
situations.
A new ctest option --repeat mode:n was added, where n is the maximum
number of times a test will be run and mode is one of the following:
until-fail
until-fail
--repeat-until-fail选项相对应,是为了保持一致性而提供的。
--repeat-until-fail option and is provided for
consistency.
until-pass
until-pass
after-timeout
after-timeout
n来避免重复超时,从而显着延长总体测试时间。
n should
typically be used to avoid having repeated timeouts significantly extend the
overall test time.
对于大型项目或测试需要花费大量时间才能完成的项目,最大化测试吞吐量可能是一个重要的考虑因素。并行运行测试的能力是它的一个关键功能ctest,并且可以使用与标准make工具非常相似的命令行选项来启用。该-j选项可用于指定可以同时运行的测试数量的上限。与大多数make实现不同,必须提供一个值,否则该选项将不起作用。作为替代方案,
CTEST_PARALLEL_LEVEL可以使用环境变量来指定作业数量,但如果同时使用两者,则命令行选项优先。这种安排对于持续集成构建特别有用,因为CTEST_PARALLEL_LEVEL可以设置为每台机器上的 CPU 核心数量,使每个项目不必自己计算最佳作业数量。对于那些需要限制并行作业数量的项目,他们仍然可以CTEST_PARALLEL_LEVEL使用-j
命令行选项进行覆盖。
Maximizing the test throughput can be an important consideration for large
projects or where tests take a non-trivial amount of time to complete. The
ability to run tests in parallel is a key feature of ctest and is enabled
using command line options that are very similar to the standard make tool.
The -j option can be used to specify an upper limit on how many tests can be
run simultaneously. Unlike most make implementations, a value must be
supplied or the option will have no effect. As an alternative, the
CTEST_PARALLEL_LEVEL environment variable can be used to specify the
number of jobs, but the command line option takes precedence if both are used.
This arrangement is particularly useful for continuous integration builds,
since CTEST_PARALLEL_LEVEL can be set to the number of CPU cores on each
machine, freeing every project from having to compute the optimal number of
jobs themselves. For those projects that need to restrict the number of
parallel jobs, they can still override CTEST_PARALLEL_LEVEL with the -j
command line option.
一个相关选项-l用于指定 CPU 负载的理想上限。ctest如果可能导致负载超过此限制,将尝试避免开始新的测试。不幸的是,这个选项的缺点在测试开始时就立即显现出来。通常,最初将启动与作业限制或设置允许的ctest数量相同的测试
,超出 指定的任何限制。测量的 CPU 负载通常具有滞后性,这使得在测量的负载增加之前首先启动过多的测试。为了防止这种情况发生,由或指定的并行作业数量应设置为不超过 所施加的限制。如果 或均未设置,则该选项将无效。尽管有这些限制,该选项仍然有助于减少共享系统上的 CPU 过载,因为其他进程也可能会竞争 CPU 资源。-jCTEST_PARALLEL_LEVEL-lctest-jCTEST_PARALLEL_LEVEL-l-jCTEST_PARALLEL_LEVEL-l-l
A related option is -l which is used to specify a desirable upper limit on
the CPU load. ctest will try to avoid starting a new test if it may cause the
load to go above this limit. Unfortunately, the shortcomings of this option are
immediately apparent at the start of testing. Typically, ctest will initially
launch as many tests as the job limit from -j or CTEST_PARALLEL_LEVEL
settings allow, exceeding any limit specified by -l. The measured CPU load
usually has a lag, which allows ctest to start too many tests initially before
the measured load increases. To prevent this occurring, the number of parallel
jobs specified by -j or CTEST_PARALLEL_LEVEL should be set to no more than
the limit imposed by -l. If neither -j nor CTEST_PARALLEL_LEVEL is set,
the -l option will have no effect. Despite these limitations, the -l option
can still be useful in helping to reduce CPU overload on shared systems where
other processes may also be competing for CPU resources.
默认情况下,ctest将假设每个测试消耗一个 CPU。对于使用多个 CPU 的测试用例,PROCESSORS可以将其测试属性设置为指示预期使用多少个 CPU。ctest然后,在开始测试之前确定是否有足够的 CPU 资源可用时,将使用该值。如果PROCESSORS设置为高于作业限制的值,ctest
则在确定是否可以开始测试时将表现得如同设置为作业限制一样。
By default, ctest will assume each test consumes one CPU. For test cases that
use more than one CPU, their PROCESSORS test property can be set to
indicate how many CPUs they are expected to use. ctest will then use that
value when determining whether enough CPU resources are free before starting
the test. If PROCESSORS is set to a value higher than the job limit, ctest
will behave as though it was set to the job limit when determining whether the
test can be started.
这些选项的效果可以在以下示例中看到,这些示例使用与前面定义的相同的测试集(为简洁起见,省略了测试摘要)。
The effect of these options can be seen in the following examples, which use the same set of tests as defined earlier (test summaries have been omitted for brevity).
ctest-j 5
测试项目 /path/to/build/dir
开始 5:Other_Foo
开始 2:仅酒吧
开始 3:FooWithBar
开始 1:FooOnly
开始 4:FooSpecial
1/5 测试 #4:FooSpecial ......................通过 0.12 秒
2/5 测试#1:FooOnly .................通过了 0.52 秒
3/5 测试 #3:FooWithBar ......................通过了 1.01 秒
4/5 测试#2:BarOnly .................通过了 1.52 秒
5/5 测试 #5:Other_Foo ......................通过了 2.02 秒ctest -j 5
Test project /path/to/build/dir
Start 5: Other_Foo
Start 2: BarOnly
Start 3: FooWithBar
Start 1: FooOnly
Start 4: FooSpecial
1/5 Test #4: FooSpecial .............. Passed 0.12 sec
2/5 Test #1: FooOnly ................. Passed 0.52 sec
3/5 Test #3: FooWithBar .............. Passed 1.01 sec
4/5 Test #2: BarOnly ................. Passed 1.52 sec
5/5 Test #5: Other_Foo ............... Passed 2.02 sec定义了 5 个测试,并且在命令行上给出的作业限制为 5,因此ctest能够立即开始所有测试。每个测试的结果是在完成时记录的,而不是按照开始的顺序记录的。如果作业限制减少到 2,输出可能更像以下内容:
Five tests were defined and the job limit was given on the command line as 5,
so ctest was able to start all tests immediately.
The result of each test was recorded as it completed, not in the order they
were started.
If the job limit is reduced to 2, the output may be more like the following:
ctest-j 2
测试项目 /path/to/build/dir
开始 5:Other_Foo
开始 2:仅酒吧
1/5 测试#2:BarOnly .................通过了 1.52 秒
开始 3:FooWithBar
2/5 测试 #5:Other_Foo ......................通过了 2.01 秒
开始 1:FooOnly
3/5 测试#1:FooOnly .................通过了 0.52 秒
开始 4:FooSpecial
4/5 测试 #3:FooWithBar ......................通过了 1.02 秒
5/5 测试 #4:FooSpecial ......................通过 0.12 秒ctest -j 2
Test project /path/to/build/dir
Start 5: Other_Foo
Start 2: BarOnly
1/5 Test #2: BarOnly ................. Passed 1.52 sec
Start 3: FooWithBar
2/5 Test #5: Other_Foo ............... Passed 2.01 sec
Start 1: FooOnly
3/5 Test #1: FooOnly ................. Passed 0.52 sec
Start 4: FooSpecial
4/5 Test #3: FooWithBar .............. Passed 1.02 sec
5/5 Test #4: FooSpecial .............. Passed 0.12 sec由于测试数量较多且工作限制较高,因此很难跟踪每个单独测试的开始和完成的记录。运行结束时的总体测试摘要变得更加重要,每个未通过的测试及其结果都会列出。--progressCMake 3.13 中添加的选项也ctest可以帮助减少输出并专注于重要细节。它将开始和完成进度消息折叠到一行,类似于 Ninja 构建工具的输出。
With a large number of tests and a high job limit, the logging of each
individual test start and completion can be difficult to follow.
The overall test summary at the end of the run then becomes much more
important, with each test that didn’t pass listed along with its result.
The --progress option to ctest, which was added in CMake 3.13, can also
help reduce the output and focus on the important details.
It collapses the start and completion progress messages down to a single line,
similar to the output of the Ninja build tool.
测试有时需要确保没有其他测试与它们并行运行。他们可能正在执行对机器上其他活动敏感的操作,或者他们可能会创造干扰其他测试的条件。要强制执行此约束,可以将测试的RUN_SERIAL属性设置为 true。
Tests sometimes need to ensure that no other test is running in parallel with
them.
They may be performing an action that is sensitive to other activities on the
machine or they may create conditions that would interfere with other tests.
To enforce this constraint, the test’s RUN_SERIAL property can be set to
true.
RUN_SERIAL是一个残酷的约束,会对测试吞吐量产生很大的影响。测试RESOURCE_LOCK属性通常是更好的选择。它提供了测试需要独占访问的资源列表。这些资源是任意字符串,ctest不会以任何方式解释,除非确保不会RESOURCE_LOCK同时运行对其自己的属性中列出的任何资源进行的其他测试。这是序列化需要独占访问某些内容(例如数据库、共享内存)的测试的好方法,而不会阻止不使用该资源的测试。
RUN_SERIAL is a brutal constraint which can have a strong impact on test
throughput.
The RESOURCE_LOCK test property is often a better alternative.
It provides a list of resources the test needs exclusive access to.
These resources are arbitrary strings which ctest does not interpret in any
way, except to ensure no other test with any of those resources listed in its
own RESOURCE_LOCK property will run at the same time.
This is a great way to serialize tests that need exclusive access to something
(e.g. a database, shared memory) without blocking tests that do not use that
resource.
set_tests_properties(FooOnly FooSpecial Other_Foo
PROPERTIES RESOURCE_LOCK Foo
)
set_tests_properties(BarOnly
PROPERTIES RESOURCE_LOCK Bar
)
set_tests_properties(FooWithBar
PROPERTIES RESOURCE_LOCK "Foo;Bar"
)set_tests_properties(FooOnly FooSpecial Other_Foo
PROPERTIES RESOURCE_LOCK Foo
)
set_tests_properties(BarOnly
PROPERTIES RESOURCE_LOCK Bar
)
set_tests_properties(FooWithBar
PROPERTIES RESOURCE_LOCK "Foo;Bar"
)
以下示例输出(同样省略了测试摘要)显示,即使 5 的作业限制允许同时执行所有测试,也会ctest延迟启动某些测试,直到它们所需的资源可用为止。
The following sample output (again with the test summary omitted) shows that
even though the job limit of 5 would allow all tests to be executed
simultaneously, ctest delays starting some tests until the resources they
need are available.
ctest-j 5
测试项目 /path/to/build/dir
开始 5:Other_Foo
开始 2:仅酒吧
1/5 测试#2:BarOnly .................通过了 1.52 秒
2/5 测试 #5:Other_Foo ......................通过了 2.02 秒
开始 3:FooWithBar
3/5 测试 #3:FooWithBar ......................通过了 1.01 秒
开始 1:FooOnly
4/5 测试#1:FooOnly .................通过了 0.52 秒
开始 4:FooSpecial
5/5 测试 #4:FooSpecial ......................通过 0.12 秒ctest -j 5
Test project /path/to/build/dir
Start 5: Other_Foo
Start 2: BarOnly
1/5 Test #2: BarOnly ................. Passed 1.52 sec
2/5 Test #5: Other_Foo ............... Passed 2.02 sec
Start 3: FooWithBar
3/5 Test #3: FooWithBar .............. Passed 1.01 sec
Start 1: FooOnly
4/5 Test #1: FooOnly ................. Passed 0.52 sec
Start 4: FooSpecial
5/5 Test #4: FooSpecial .............. Passed 0.12 sec当测试需要对某些内容进行独占访问时,测试RESOURCE_LOCK属性非常适合,但当需要对测试资源进行更细粒度的控制时,RESOURCE_GROUPSCMake 3.16 或更高版本中提供的测试属性可能更合适。资源组使项目不仅可以定义测试需要哪些资源,还可以定义每种资源需要多少。这允许受控的资源共享,并且可用于各种有趣的场景:
The RESOURCE_LOCK test property is a good fit when a test needs exclusive
access to something, but when more fine-grained control over test resources is
needed, the RESOURCE_GROUPS test property available with CMake 3.16 or
later may be more appropriate.
Resource groups enable projects to define not just what resources a test
needs, but also how much of each resource is required.
This allows controlled sharing of resources and can be useful for a variety of
interesting scenarios:
ctest将跟踪分配给所有当前正在运行的测试的内存资源,并确保不超过可用内存资源,从而延迟测试执行,直到满足该测试的内存要求。
ctest scheduler will keep track of the memory resources it has
allocated to all currently running tests and ensure that the available memory
resources are not exceeded, delaying test execution until that test’s memory
requirements can be met.
ctest调度程序将负责将测试分配到运行时可用的 GPU 数量上。
ctest scheduler will take care of distributing the tests across however
many GPUs are available to it at run time.
与 相比RESOURCE_LOCK,设置资源组要复杂得多,需要许多单独的步骤:
Compared to RESOURCE_LOCK, setting up resource groups is considerably more
involved, requiring a number of separate steps:
ctest
环境变量传递给它的资源详细信息。
ctest
via environment variables.
测试将其所需的资源定义为资源组列表。每组由一对或多name:value对组成,多对以逗号分隔。该name对的一部分称为资源类型。mem_gb在以下示例中,该组指定了 16 个单位和 4的要求
cpus:
A test defines its required resources as a list of resource groups.
Each group consists of one or more name:value pairs, with multiple pairs
separated by commas.
The name part of the pair is referred to as the resource type.
In the following example, the group specifies a requirement for 16 units of
mem_gb and 4 cpus:
mem_gb:16,CPU:4
mem_gb:16,cpus:4
一个组之前还可以有整个组需要多少个的计数。它以整数形式给出,后跟逗号,然后是如上所述的组定义。当省略此计数值时,如上例所示,假定为 1。下面演示如何指定测试需要 3 组资源,其中每组需要 2gpus和 4 workers:
A group can also be preceded by a count of how many of that whole group are
needed.
It is given as an integer followed by a comma and then the group definition as
described above.
When this count value is omitted, as in the above example, it is assumed to be
1.
The following demonstrates how to specify that the test requires 3 sets of
resources where each set needs 2 gpus and 4 workers:
3、GPU:2、工人:4
3,gpus:2,workers:4
组还可以多次列出特定资源类型。考虑以下示例:
A group can also list a particular resource type more than once. Consider the following example:
显卡:2,显卡:4
gpus:2,gpus:4
上述组总共需要 6 个gpus,但它们可以分为gpus资源类型的两个单独的实例(实例将在下一节中讨论)。允许 2 个gpus来自一个实例,4 个来自另一个实例。gpus如果有可用的插槽,他们也可能会对一个实例上的 6 个全部感到满意。
The above group needs a total of 6 gpus, but they can be split across two
separate instances of the gpus resource type (instances are discussed in the
next section).
It is permitted for 2 gpus to come from one instance and 4 from the
other.
They could also be satisfied with 6 gpus all on the one instance if it has
the slots available.
测试可能需要多组资源组,其中这些组并不完全相同。酒店RESOURCE_GROUPS接受正是出于此目的的列表。例如:
A test may need multiple sets of resource groups where the groups are not all
the same.
The RESOURCE_GROUPS property accepts a list for exactly this purpose.
For example:
set_property(TEST ParallelCoordinator PROPERTY
RESOURCE_GROUPS
producers:1,consumers:1
producers:1,consumers:4
producers:4,consumers:1
4,producers:1,consumers:1
)set_property(TEST ParallelCoordinator PROPERTY
RESOURCE_GROUPS
producers:1,consumers:1
producers:1,consumers:4
producers:4,consumers:1
4,producers:1,consumers:1
)
上述规范导致测试ParallelCoordinator总共需要七个资源组。除非有足够的资源可以同时满足所有七个组的需求,否则不会执行测试。
The above specification results in the ParallelCoordinator test requiring a
total of seven resource groups.
The test won’t be executed unless enough resources are available to satisfy all
seven groups at once.
能够将总资源需求分配到多个组对于支持某些系统资源配置至关重要。下一节将讨论利用此功能的场景。
Being able to split the total resource requirements across multiple groups is essential for supporting certain system resource configurations. The next section discusses scenarios that take advantage of this capability.
为了ctest给测试分配资源,需要知道系统上有哪些资源可用。ctest
系统资源在 JSON 文件中指定,该文件通过以下方式之一传递(按优先顺序列出):
In order for ctest to allocate resources to tests, it needs to be told what
resources are available on the system.
The system resources are specified in a JSON file, which is passed to ctest
in one of the following ways (listed in order of precedence):
RESOURCE_SPEC_FILE调用中使用关键字(请参阅第 25.9 节 “CDash 集成”)。ctest_test()
RESOURCE_SPEC_FILE keyword in a call to ctest_test() within a
CDash script (see Section 25.9, “CDash Integration”).
CTEST_RESOURCE_SPEC_FILE在 CDash 脚本中设置变量或ctest -D在运行 CDash 脚本时将变量设置为命令行选项。此变量充当RESOURCE_SPEC_FILE调用中关键字的默认值ctest_test(),但仅 CMake 3.18 或更高版本支持它。
CTEST_RESOURCE_SPEC_FILE variable either in a CDash script
or as a ctest -D command line option when running a CDash script.
This variable acts as a default value for the RESOURCE_SPEC_FILE keyword in
calls to ctest_test(), but it is only supported with CMake 3.18 or later.
--resource-spec-file命令行选项ctest。
--resource-spec-file command line option to ctest.
CTEST_RESOURCE_SPEC_FILE为 CMake 变量(仅受 CMake 3.18 或更高版本支持)。为了确保用户始终拥有控制权,应仅使用命令行选项设置此变量cmake -D,而不是直接在项目中对其进行硬编码。
CTEST_RESOURCE_SPEC_FILE as a CMake variable (only supported with
CMake 3.18 or later).
To ensure the user always has control, this variable should only be set using
a cmake -D command line option rather than hard-coding it directly in the
project.
这个 JSON 文件的格式可以认为如下(更正式的描述可以在命令的 CMake 文档中找到ctest):
The format of this JSON file can be thought of as follows (a more formal
description can be found in the CMake documentation of the ctest command):
{
"version": {
"major": 1,
"minor": 0
},
"local": [
{
"resource1": [ ... ],
"resource2": [ ... ],
...
}
]
}{
"version": {
"major": 1,
"minor": 0
},
"local": [
{
"resource1": [ ... ],
"resource2": [ ... ],
...
}
]
}
该version对象必须存在于顶层,并且必须同时包含 a
major和 aminor元素。CMake 3.16 中资源分配功能的第一个版本要求
major为 1 和minor0。未来版本可能支持其他版本组合。
The version object must exist at the top level and it must contain both a
major and a minor element.
The first release of the resource allocation feature in CMake 3.16 requires
major to be 1 and minor to be 0.
Future releases may support other version combinations.
另一个顶级 JSON 元素必须被命名local,并且它必须是一个只有一个元素的数组(未来的 CMake 版本可能允许更多)。该数组元素是一个 JSON 对象,它定义了将运行测试的系统提供的每种资源类型。每个资源类型的名称必须全部小写,并且可以包含数字和下划线,但不能以数字开头。每个资源类型都指定为以下格式的项目数组:
The other top level JSON element must be named local and it must be an array
with exactly one element (future CMake releases may allow more).
That array element is a JSON object that defines each of the resource types
provided by the system on which the tests will run.
The name of each resource type must be all lowercase and may contain numbers
and underscores, but it must not start with a number.
Each resource type is specified as an array of items of the following format:
{
"id": "name",
"slots": numericValue
}{
"id": "name",
"slots": numericValue
}
是id用于标识该特定资源实例的名称。此名称在该资源类型的所有实例中必须是唯一的,并且只能包含小写字母、数字或下划线。该名称不需要以字母或下划线开头,如果需要,它可以只包含数字。指定slots该实例提供的资源量,并且必须以整数形式给出。
The id is the name used to identify this particular instance of the resource.
This name must be unique across all instances for this resource type and it
must only contain lowercase letters, numbers or underscores.
The name is not required to start with a letter or underscore and it can
contain just a number if desired.
The slots specifies the amount of the resource that this instance provides
and it must be given as an integer.
{
"version": {
"major": 1,
"minor": 0
},
"local": [
{
"mem_gb": [
{
"id": "pool_0",
"slots": 64
}
],
"gpus": [
{
"id": "0",
"slots": 2
},
{
"id": "1",
"slots": 2
}
],
"workers": [
{
"id": "0",
"slots": 8
},
{
"id": "1",
"slots": 4
}
]
}
]
}{
"version": {
"major": 1,
"minor": 0
},
"local": [
{
"mem_gb": [
{
"id": "pool_0",
"slots": 64
}
],
"gpus": [
{
"id": "0",
"slots": 2
},
{
"id": "1",
"slots": 2
}
],
"workers": [
{
"id": "0",
"slots": 8
},
{
"id": "1",
"slots": 4
}
]
}
]
}
上面的示例演示了id同一资源类型中的值只需是唯一的。和gpus的workersid 均为 0 和 1,但这没关系,因为它们是不同的资源类型。
The above example demonstrates that values for the id only need to be unique
within the same resource type.
Both gpus and workers have ids of 0 and 1, but that is okay because they
are different resource types.
由于无法使用给定的值来指定单位slots,因此建议在资源类型的标签中包含单位,其中值的含义需要某种单位。在上面的示例中,mem_gb资源类型的名称清楚地表明槽将被解释为千兆字节。gpus在和的情况下workers,不需要任何单位,因为已经清楚该slots值是这些资源的计数。对于不需要单位的资源类型名称,惯例是名称通常应采用复数形式。
Because units cannot be specified with the value given to slots, it may be
advisable to include units in the label for the resource type where the value’s
meaning requires some sort of unit.
In the example above, the mem_gb resource type’s name makes it clear that the
slots are to be interpreted as gigabytes.
In the case of gpus and workers, no units are needed, since it is already
clear that the slots value is a count of those resources.
For resource type names that don’t require units, the convention is that names
should generally be in plural form.
当ctest尝试从可用资源池中满足测试的资源需求时,它不会将所有测试的资源组合并在一起。相反,它会逐个迭代资源组,并尝试单独满足每个组的要求。对于每个组,每name:value对都进行评估。
ctest将查找资源类型的系统分配,并尝试在该资源类型的数组中查找具有足够未分配插槽以满足该资源需求的项目。重要的是,ctest不会组合多个数组元素的槽来尝试满足一name:value对的资源要求。
When ctest tries to satisfy the resource requirements of a test from the
available pool of resources, it does not merge all the test’s resource groups
together.
Rather, it iterates over the resource groups one by one and tries to satisfy
each group individually.
For each group, each name:value pair is assessed.
ctest will look up the system allocations for the resource type and try to
find an item in that resource type’s array that has enough slots unallocated to
satisfy that resource requirement.
Importantly, ctest will not combine slots from multiple array elements to try
to meet one name:value pair’s resource requirements.
一个示例有助于演示分配逻辑。考虑上面给出的资源规范文件和定义为
gpus:4. 系统总共有 4 个gpus资源类型槽,但它们被分为两个 ID 为 0 和 1 的单独项目。因为ctest只允许name:value从资源类型数组的单个元素满足资源需求,所以此需求不能满意,测试将无法运行。相反,如果测试的资源组定义为4,gpus:1,则需要 4 个单独的组,其中每个组需要一个gpus插槽。这可以得到满足,并且两个组甚至可以共享一个数组项(例如,两个组可以共享id为0的资源,另外两个组可以共享id为1的资源)。定义为的群体gpus:1,gpus:1可以通过三种不同的方式得到满足。它可以从 id 0 接收两个槽,从 id 1 接收两个槽,或者从每个槽接收一个槽。
An example helps demonstrate the allocation logic.
Consider the resource spec file given above and a resource group defined as
gpus:4.
The system has a combined total of 4 slots of the gpus resource type, but
they are split across two separate items with ids 0 and 1.
Because ctest is only allowed to satisfy a name:value resource requirement
from a single element of a resource type’s array, this requirement cannot be
satisfied and the test will fail to run.
Conversely, if a test had a resource group definition of 4,gpus:1, it
requires 4 separate groups where each group needs one gpus slot.
This can be satisfied and two groups can even share one array item (e.g. two
groups can share the resource with id 0 and the other two groups can share
resource id 1).
A group defined as gpus:1,gpus:1 could be satisfied three different ways.
It could receive both slots from id 0, both slots from id 1, or one slot from
each.
该测试通过许多环境变量接收有关分配给它的资源的信息。其中最基本的是CTEST_RESOURCE_GROUP_COUNT,它将保存测试指定的资源组的总数。如果没有定义该环境变量,则意味着ctest调用时没有提供资源规范文件。在这种情况下,由测试来决定该怎么做。如果在没有提供资源分配的情况下测试无法运行,则测试应该失败或者应该指示它已被跳过(例如,通过与测试SKIP_RETURN_CODE属性匹配的返回代码或通过与SKIP_REGULAR_EXPRESSION.
The test receives information about the resources allocated to it through a
number of environment variables.
The most basic of these is CTEST_RESOURCE_GROUP_COUNT, which will holds
the total number of resource groups the test specified.
If this environment variable is not defined, it means that no resource spec
file was provided when ctest was invoked.
It is up to the test to then decide what to do in such cases.
If the test cannot run without resource allocations being provided, the test
should either fail or it should indicate that it has been skipped (e.g. by a
return code that matches the SKIP_RETURN_CODE test property or by output
that matches a SKIP_REGULAR_EXPRESSION.
对于每个资源组,将有一组环境变量,其模式CTEST_RESOURCE_GROUP_<num>包含为该组分配的资源类型列表。为了准确查看给定资源类型的哪些资源被分配,
CTEST_RESOURCE_GROUP_<num>_<resourceType>必须查阅另一组该形式的环境变量。这<resourceType>将是资源类型的大写名称。这些变量的内容将是一个包含一个或多个表单项的列表id:X,slots:Y,可以读取为“来自 id X 的 Y 个槽”。
For each resource group, there will be a set of environment variables with the
pattern CTEST_RESOURCE_GROUP_<num> which contains a list of resource
types allocated for that group.
In order to see exactly which resources of a given resource type were
allocated, another set of environment variables of the form
CTEST_RESOURCE_GROUP_<num>_<resourceType> must be consulted.
The <resourceType> will be the uppercased name of the resource type.
The contents of these variables will be a list containing one or more
items of the form id:X,slots:Y, which can be read as "Y slots from id X".
为了说明这一点,上一节中的一个示例将一组资源组指定为4,gpus:1。这可能会导致测试接收一组如下所示的环境变量:
To illustrate, one example in the previous section specified a set of resource
groups as 4,gpus:1.
This might lead to the test receiving a set of environment variables like the
following:
CTEST_RESOURCE_GROUP_COUNT=4 CTEST_RESOURCE_GROUP_0=GPU CTEST_RESOURCE_GROUP_1=GPU CTEST_RESOURCE_GROUP_2=GPU CTEST_RESOURCE_GROUP_3=GPU CTEST_RESOURCE_GROUP_0_GPUS=id:0,插槽:1 CTEST_RESOURCE_GROUP_1_GPUS=id:0,插槽:1 CTEST_RESOURCE_GROUP_2_GPUS=id:1,插槽:1 CTEST_RESOURCE_GROUP_3_GPUS=id:1,插槽:1
CTEST_RESOURCE_GROUP_COUNT=4 CTEST_RESOURCE_GROUP_0=gpus CTEST_RESOURCE_GROUP_1=gpus CTEST_RESOURCE_GROUP_2=gpus CTEST_RESOURCE_GROUP_3=gpus CTEST_RESOURCE_GROUP_0_GPUS=id:0,slots:1 CTEST_RESOURCE_GROUP_1_GPUS=id:0,slots:1 CTEST_RESOURCE_GROUP_2_GPUS=id:1,slots:1 CTEST_RESOURCE_GROUP_3_GPUS=id:1,slots:1
再举一个例子,考虑一个具有一个资源组的测试,其中该组定义为gpus:2,gpus:2,workers:4。它可以接收的一组可能的环境变量是:
For another example, consider a test that has one resource group, where that
group is defined as gpus:2,gpus:2,workers:4.
A possible set of environment variables it could receive would be:
CTEST_RESOURCE_GROUP_COUNT=1 CTEST_RESOURCE_GROUP_0=GPU、工作人员 CTEST_RESOURCE_GROUP_0_GPUS=id:0,插槽:2;id:1,插槽:2 CTEST_RESOURCE_GROUP_0_WORKERS=id:0,槽位:4
CTEST_RESOURCE_GROUP_COUNT=1 CTEST_RESOURCE_GROUP_0=gpus,workers CTEST_RESOURCE_GROUP_0_GPUS=id:0,slots:2;id:1,slots:2 CTEST_RESOURCE_GROUP_0_WORKERS=id:0,slots:4
请注意如何返回两个列表项,CTEST_RESOURCE_GROUP_0_GPUS因为资源组列出了gpus:2两次。
Note how two list items are returned for CTEST_RESOURCE_GROUP_0_GPUS because
the resource group listed gpus:2 twice.
这取决于测试它如何使用通过环境变量提供的信息,但至少应该始终确认是否已
CTEST_RESOURCE_GROUP_COUNT定义。
It is up to the test how it uses the information provided through the
environment variables, but at the very least it should always confirm whether
CTEST_RESOURCE_GROUP_COUNT is defined.
测试不仅可以用于简单地验证特定条件,还可以用于强制执行它们。例如,一项测试可能需要连接到服务器,以便它可以验证客户端实现。可以创建另一个测试用例来确保服务器正在运行,而不是依赖开发人员来确保这样的服务器可用。然后,客户端测试需要对服务器测试有某种依赖性,以确保它们以正确的顺序运行。
Tests can be used to do more than simply verify a particular condition, they can also be used to enforce them. For example, one test may need a server to connect to so that it can verify a client implementation. Rather than relying on the developer to ensure such a server is available, another test case can be created which ensures a server is running. The client test then needs to have some kind of dependency on the server test to make sure they are run in the correct order.
testDEPENDS属性允许通过保存在该测试运行之前必须完成的其他测试的列表来表达此约束的某种形式。上面的客户端/服务器示例可以大致表达如下:
The DEPENDS test property allows a form of this constraint to be
expressed by holding a list of other tests that must complete before that test
can run. The above client/server example could loosely be expressed as follows:
set_tests_properties(ClientTest1 ClientTest2
PROPERTIES DEPENDS StartServer
)
set_tests_properties(StopServer
PROPERTIES DEPENDS "ClientTest1;ClientTest2"
)set_tests_properties(ClientTest1 ClientTest2
PROPERTIES DEPENDS StartServer
)
set_tests_properties(StopServer
PROPERTIES DEPENDS "ClientTest1;ClientTest2"
)
测试属性的一个弱点DEPENDS是,虽然它定义了测试顺序,但它没有考虑先决测试是通过还是失败。在上面的示例中,如果StartServer测试用例失败,ClientTest1、
ClientTest2和StopServer测试仍将运行。这些测试可能会失败,并且测试输出将显示所有四个测试都失败,而实际上只有测试StartServer失败,其他测试应该被跳过。
A weakness with the DEPENDS test property is that while it defines a test
order, it does not consider whether the pre-requisite tests pass or fail. In
the above example, if the StartServer test case fails, the ClientTest1,
ClientTest2 and StopServer tests will still run. These tests will then
likely fail and the test output will show all four tests as failed, where in
reality only the StartServer test failed and the others should have been
skipped.
CMake 3.7 添加了对测试装置的支持,这一概念允许更严格地表达测试之间的依赖关系。测试可以通过在其
FIXTURES_REQUIRED测试属性中列出该固定装置名称来指示它需要特定的固定装置。FIXTURES_SETUP在启动相关测试之前,其测试属性中具有相同固定装置名称的任何其他测试都必须成功完成。如果夹具的任何设置测试失败,则需要该夹具的所有测试将被标记为已跳过。类似地,测试可以在其测试属性中列出固定装置,以指示它必须在其属性FIXTURES_CLEANUP
中列出的相同固定装置的任何其他测试之后运行。这些清理测试不需要设置或需要固定装置的测试才能通过,因为即使早期测试失败也可能需要清理。FIXTURES_SETUPFIXTURES_REQUIRED
CMake 3.7 added support for test fixtures, a concept which allows dependencies
between tests to be expressed much more rigorously. A test can indicate it
requires a particular fixture by listing that fixture name in its
FIXTURES_REQUIRED test property. Any other test with that same fixture
name in its FIXTURES_SETUP test property must complete successfully
before the dependent test will be started. If any of the setup tests for a
fixture fail, all of the tests that require that fixture will be marked as
skipped. Similarly, a test can list a fixture in its FIXTURES_CLEANUP
test property to indicate that it must be run after any other test with that
same fixture listed in its FIXTURES_SETUP or FIXTURES_REQUIRED property.
These cleanup tests do not require the setup or fixture-requiring tests to
pass, since cleanup may be needed even if the earlier tests fail.
所有三个与夹具相关的测试属性都接受夹具名称列表。这些名称是任意的,不必与测试名称、它们使用的资源或任何其他属性相关。夹具名称应该让开发人员清楚地了解它们所代表的含义,因此,虽然不要求这样做,但它们通常具有与RESOURCE_LOCK属性所用的值相同的值。
All three fixture-related test properties accept a list of fixture names. These
names are arbitrary and do not have to relate to the test names, resources they
use or any other property. The fixture names should make clear to developers
what they represent and so, while not required to, they often do have the same
value as those used for RESOURCE_LOCK properties.
考虑前面的客户端/服务器示例。这可以使用具有以下属性的装置来严格表达:
Consider the earlier client/server example. This can be expressed rigorously using fixtures with the following properties:
set_tests_properties(StartServer
PROPERTIES FIXTURES_SETUP Server
)
set_tests_properties(ClientTest1 ClientTest2
PROPERTIES FIXTURES_REQUIRED Server
)
set_tests_properties(StopServer
PROPERTIES FIXTURES_CLEANUP Server
)set_tests_properties(StartServer
PROPERTIES FIXTURES_SETUP Server
)
set_tests_properties(ClientTest1 ClientTest2
PROPERTIES FIXTURES_REQUIRED Server
)
set_tests_properties(StopServer
PROPERTIES FIXTURES_CLEANUP Server
)
上面的Server是装置的名称,ClientTest1只有
通过后ClientTest2才会运行StartServer,并且StopServer无论其他三个测试的结果如何,都会最后运行。如果启用并行执行,StartServer将首先运行,两个客户端测试将同时运行,并且StopServer仅在两个客户端测试完成或跳过后才运行。
In the above, Server is the name of the fixture, ClientTest1 and
ClientTest2 will only run if StartServer passes and StopServer will run
last regardless of the result of any of the other three tests. If parallel
execution is enabled, StartServer will run first, the two client tests will
run simultaneously and StopServer will only run after both client tests have
been completed or skipped.
当开发人员仅运行测试的子集时,可以看到固定装置的另一个好处。ClientTest2考虑开发人员正在工作但对运行不感兴趣的场景
ClientTest1。当使用 表达测试之间的依赖关系时DEPENDS,开发人员有责任确保他们还在测试集中包含所需的测试,这意味着他们需要了解所有相关的依赖关系。这将导致
ctest命令行:
Another benefit of fixtures can be seen when the developer is running only a
subset of tests. Consider the scenario where the developer is working on
ClientTest2 and is not interested in running ClientTest1. When dependencies
between tests are expressed using DEPENDS, the developer is responsible for
ensuring they also include required tests in the test set, which means they
need to understand all the relevant dependencies. This would lead to the
ctest command line:
ctest -R“启动服务器|客户端测试2|停止服务器”
ctest -R "StartServer|ClientTest2|StopServer"
使用夹具时,ctest会自动将任何设置或清理测试添加到要执行的测试集中,以满足夹具要求。这意味着开发人员只需要指定他们想要关注的测试并将依赖项留给ctest:
When fixtures are used, ctest automatically adds any setup or cleanup tests
to the set of tests to be executed in order to satisfy fixture requirements.
This means the developer need only specify the test they want to focus on and
leave the dependencies to ctest:
ctest -R ClientTest2
ctest -R ClientTest2
使用该--rerun-failed选项时,相同的机制可确保设置和清理测试自动添加到测试集中,以满足先前失败的测试的夹具依赖性。
When using the --rerun-failed option, this same mechanism ensures that setup
and cleanup tests are automatically added to the test set to satisfy
fixture dependencies of the previously failed tests.
夹具可以具有零个或多个设置测试和零个或多个清理测试。夹具可以定义没有清理测试的设置测试,反之亦然。虽然不是特别有用,但固定装置可以根本没有设置或清理测试,在这种情况下,固定装置对要执行的测试或测试运行的时间没有影响。类似地,夹具可以具有与其关联的设置和/或清理测试,但没有需要它的测试。在开发过程中,当定义或暂时禁用测试时,可能会出现这些情况。对于没有需要它的测试的固定装置的情况,CMake 3.7 中的一个错误允许该固定装置的清理测试在设置测试之前运行,但该错误在 3.8.0 版本中已修复。
A fixture may have zero or more setup tests and zero or more cleanup tests. Fixtures may define setup tests with no cleanup tests and vice versa. While not particularly useful, a fixture can have no setup or cleanup tests at all, in which case the fixture has no effect on the tests to be executed or when the tests will run. Similarly, a fixture can have setup and/or cleanup tests associated with it but no tests that require it. These situations can arise during development when tests are being defined or temporarily disabled. For the case of a fixture having no tests that require it, a bug in CMake 3.7 allowed that fixture’s cleanup tests to run before the setup tests, but that bug was fixed in the 3.8.0 release.
一个更复杂的示例演示了如何使用固定装置来表达更复杂的测试依赖性。扩展前面的示例,假设一个客户端测试仅需要一台服务器,而另一个客户端测试则需要服务器和数据库都可用。通过定义两个装置可以简洁地表达这一点:Server和Database。对于后者,简单地检查是否有可用的数据库并在没有可用的情况下失败是可以接受的,因此该Database
装置不需要清理测试。和夹具不相关,因此它们之间不需要依赖关系Server。Database这些约束可以这样表达:
A more involved example demonstrates how fixtures can be used to express more
complex test dependencies. Expanding the previous example, suppose one client
test requires just a server, whereas another requires both a server and a
database to be available. This is succinctly expressed by defining two
fixtures: Server and Database. For the latter, it is acceptable to simply
check whether there is a database available and fail if not, so the Database
fixture requires no cleanup test. The Server and Database fixtures are not
related, so they need no dependencies between them. These constraints can be
expressed like so:
# Setup/cleanup
set_tests_properties(StartServer
PROPERTIES FIXTURES_SETUP Server
)
set_tests_properties(StopServer
PROPERTIES FIXTURES_CLEANUP Server
)
set_tests_properties(EnsureDbAvailable
PROPERTIES FIXTURES_SETUP Database
)
# Client tests
set_tests_properties(ClientNoDb
PROPERTIES FIXTURES_REQUIRED Server
)
set_tests_properties(ClientWithDb
PROPERTIES FIXTURES_REQUIRED "Server;Database"
)# Setup/cleanup
set_tests_properties(StartServer
PROPERTIES FIXTURES_SETUP Server
)
set_tests_properties(StopServer
PROPERTIES FIXTURES_CLEANUP Server
)
set_tests_properties(EnsureDbAvailable
PROPERTIES FIXTURES_SETUP Database
)
# Client tests
set_tests_properties(ClientNoDb
PROPERTIES FIXTURES_REQUIRED Server
)
set_tests_properties(ClientWithDb
PROPERTIES FIXTURES_REQUIRED "Server;Database"
)
虽然ctest自动将夹具依赖项添加到测试执行集中通常是一个有用的功能,但有时这可能是不可取的。继续上面的示例,开发人员可能希望让服务器保持运行并继续多次执行一个客户端测试。他们可能会进行更改、重新编译代码并检查每次更改是否通过客户端测试。为了支持这种级别的控制,CMake 3.9 引入了-FS、-FC和-FA选项ctest,每个选项都需要一个与装置名称匹配的正则表达式。该-FS选项用于禁用为那些与所提供的正则表达式匹配的装置添加装置设置依赖项。-FC对清理测试执行相同的操作并将-FA两者结合起来,禁用匹配的设置和清理测试。一种常见的情况是根本禁止添加任何安装/清理依赖项,这可以通过给出单个句点 (.) 的正则表达式来完成。
While having ctest automatically add fixture dependencies into the test
execution set is generally a useful feature, there are also times where this
can be undesirable. Continuing with the above example, the developer may want
to leave the server running and keep executing just one client test multiple
times. They may be making changes, recompiling the code and checking whether
the client test passes with each change. To support this level of control,
CMake 3.9 introduced the -FS, -FC and -FA options to ctest, each of
which requires a regular expression that will be matched against fixture names.
The -FS option is used to disable adding fixture setup dependencies for those
fixtures that match the regular expression provided. -FC does the same for
cleanup tests and -FA combines both, disabling both setup and cleanup tests
that match. A common situation is to disable adding any setup/cleanup
dependencies at all, which can be done by giving a regular expression of a
single period (.).
下面演示了夹具控制选项及其效果的各种示例:
The following demonstrates various examples of the fixture control options and their effects:
| 命令行 | 执行集中的测试 |
|---|---|
|
|
|
|
|
|
|
|
|
|
当项目定义的可执行目标用作 的命令时
add_test(),CMake 会自动替换构建的可执行文件的位置。对于交叉编译场景,这通常不起作用,因为主机通常无法直接运行为不同平台构建的二进制文件。
When an executable target defined by the project is used as the command for
add_test(), CMake automatically substitutes the location of the built
executable. For a cross-compiling scenario, this won’t typically work, since
the host cannot usually run binaries built for a different platform directly.
为了帮助解决此问题,CMake 提供了一个CROSSCOMPILING_EMULATOR目标属性,可以将其设置为用于启动目标的脚本或可执行文件。CMake 在形成要运行的命令时会将其添加到目标二进制文件中,因此真正的目标二进制文件将成为模拟器脚本或可执行文件的第一个参数。这使得即使在交叉编译时也可以运行测试。对于 CMake 3.15 或更高版本,CROSSCOMPILING_EMULATOR可以是一个列表,允许参数包含在目标二进制文件之前插入的项目中。
To help with this, CMake provides a CROSSCOMPILING_EMULATOR target
property which can be set to a script or executable to be used to launch the
target. CMake will prepend this to the target binary when forming the command
to run, so the real target binary becomes the first argument to the emulator
script or executable.
This enables tests to be run even when cross-compiling.
With CMake 3.15 or later, CROSSCOMPILING_EMULATOR can be a list to allow
arguments to be included in the items inserted before the target binary.
不一定CROSSCOMPILING_EMULATOR是实际的模拟器,它只需是可以在主机上运行以启动目标可执行文件的命令。虽然目标平台的专用模拟器是明显的用例,但也可以将其设置为一个脚本,将可执行文件复制到目标计算机并远程运行它(例如通过 SSH 连接)。无论使用哪种方法,开发人员都应该意识到模拟器的启动时间或准备运行二进制文件的时间可能很重要,并且可能会对测试时序测量产生影响。反过来,这可能意味着可能需要修改测试超时设置。
The CROSSCOMPILING_EMULATOR doesn’t have to be an actual emulator, it just
has to be a command that can be run on the host to launch the target
executable. While a dedicated emulator for the target platform is the obvious
use case, one could also set it to a script that copies the executable to a
target machine and runs it remotely (e.g. over a SSH connection). Whichever
method is used, developers should be aware that the startup time for an
emulator or for preparing to run the binary could be non-trivial and may have
an impact on the test timing measurements. This can, in turn, mean that test
timeout settings may need to be revised.
目标属性的默认值CROSSCOMPILING_EMULATOR取自CMAKE_CROSSCOMPILING_EMULATOR变量,这是指定模拟器详细信息的常用方式,而不是单独设置每个目标的属性。该变量通常会在工具链文件中设置,因为它影响try_run()命令等内容的方式与影响测试和自定义命令的方式类似,如上所述。有关变量影响这方面的更多信息,请参阅第 22.5 节“编译器检查”中的讨论
。
The default value for the CROSSCOMPILING_EMULATOR target property is taken
from the CMAKE_CROSSCOMPILING_EMULATOR variable, which is the usual way
the emulator details would be specified rather than setting each target’s
property individually. The variable would typically be set in the toolchain
file, since it affects things like try_run() commands in a similar way to how
it affects tests and custom commands as described above. See the discussion in
Section 22.5, “Compiler Checks” for more on this aspect of the variable’s effects.
即使不进行交叉编译,CMake 仍将遵循非空
CROSSCOMPILING_EMULATOR目标属性并将其添加到命令行以用于执行该目标的测试和自定义命令。这非常有用,允许将属性临时设置为启动脚本以协助调试或数据收集等操作。不建议使用此技术作为项目构建的永久功能,但它在某些开发情况下可能很有用。
Even when not cross-compiling, CMake will still honor a non-empty
CROSSCOMPILING_EMULATOR target property and prepend it to the command line
for tests and custom commands executing that target. This can be quite useful,
allowing the property to be temporarily set to a launch script to assist with
things like debugging or for data-gathering. It is not recommended to use this
technique as a permanent feature of a project’s build, but it may be useful in
certain development situations.
ctest不仅可以执行一组测试,它还可以驱动整个配置、构建和测试管道。有两种主要方法可以做到这一点;一种更基本、独立的方法和一种与仪表板报告工具密切相关的更强大的方法。更基本的方法是
ctest使用命令行选项调用该工具--build-and-test,该选项使用以下形式:
ctest can not only execute a set of tests, it can drive an entire
configure, build and test pipeline. There are two main methods for doing this;
a more basic, standalone way and a more powerful approach closely associated
with a dashboard reporting tool. The more basic approach is to invoke the
ctest tool with the --build-and-test command line option, which uses the
following form:
ctest --构建和测试 sourceDir buildDir
--build-generator 生成器
[选项...]
[--测试命令 testCommand [参数...]]ctest --build-and-test sourceDir buildDir
--build-generator generator
[options...]
[--test-command testCommand [args...]]如果没有任何options,上面将使用指定的sourceDir
和运行 CMakebinaryDir并使用指定的generator. 所有这三个都必须指定。如果 CMake 运行成功,ctest则将构建clean
目标,最后将构建默认all目标。要在构建步骤之后运行测试,命令行上的最后一个选项必须包含
--test-command其关联的参数testCommand和可选的一些参数。这可以是ctest运行所有测试的另一次调用,如以下示例所示。
Without any options, the above will run CMake with the specified sourceDir
and binaryDir and use the specified generator. All three of these must be
specified. If the CMake run was successful, ctest will then build the clean
target and lastly it will build the default all target. To run tests as well
after the build step, the last option on the command line must be
--test-command with its associated testCommand and optionally some
arguments. This can be another invocation of ctest to run all tests, as
demonstrated in the following example.
ctest --构建和测试 sourceDir buildDir \
--build-generator 忍者 \
--测试命令 ctest -j 4ctest --build-and-test sourceDir buildDir \
--build-generator Ninja \
--test-command ctest -j 4上面执行了完整的配置-清理-构建-测试管道。提供了各种选项,可用于修改管道的哪些部分正在运行以及它们的运行方式。例如,--build-nocmake分别
--build-noclean禁用配置和清理步骤。该
--build-two-config选项将调用 CMake 两次,这会处理某些特殊情况,即需要第二次 CMake 传递才能完全配置项目。使用 Visual Studio 等生成器时,可能需要使用--build-generator-platform和
指定额外的生成器详细信息--build-generator-toolset,这些详细信息将分别作为-A和-T
选项传递到cmake配置步骤。一些生成器(例如 Xcode)可能需要提供项目名称,以便它可以找到配置阶段生成的项目文件,这可以通过该
--build-project选项来完成。可以使用该--build-target选项设置构建步骤中的构建目标,并且可以通过传递--build-makeprogram替代工具来覆盖构建工具。
The above carries out a full configure-clean-build-test pipeline. Various
options are provided which can be used to modify which parts of the pipeline
are run and how they are run. For example, --build-nocmake and
--build-noclean disable the configure and clean steps respectively. The
--build-two-config option will invoke CMake twice, which handles certain
special cases where a second CMake pass is needed to fully configure a project.
When using a generator like Visual Studio, it may be necessary to specify
extra generator details with --build-generator-platform and
--build-generator-toolset, which will be passed through as the -A and -T
options respectively to cmake for the configure step. Some generators like
Xcode may require the project name to be given so it can find the project
file generated by the configure stage, which can be done with the
--build-project option. The target to build in the build step can be set
using the --build-target option and the build tool can be overridden by
passing --build-makeprogram with the alternative tool.
从上面可以看出,所有与
--build-and-test模式相关的选项都以 开头--build。虽然大多数选项都有直观的名称,但常见的--build前缀可能会导致一些不幸的令人困惑的异常情况。存在具有该名称的选项--build-options,该选项最初似乎与构建步骤相关,但实际上用于将命令行选项传递给命令cmake。它还具有附加约束,即它必须位于命令行的最后,除非--test-command还给出了,在这种情况下--build-options必须位于 之前--test-command。
As can be seen in the above, all of the options related to the
--build-and-test mode begin with --build. While most options have intuitive
names, the common --build prefix can lead to some unfortunate confusing
anomalies. An option with the name --build-options exists which may initially
seem to be related to the build step, but is actually used to pass command line
options to the cmake command. It also has the additional constraint that it
must be last on the command line, unless --test-command is also given, in
which case --build-options must precede --test-command.
以下示例应阐明这些限制。它将两个缓存变量定义添加到cmake调用中,并在构建步骤后运行完整的测试套件。
The following example should clarify these constraints.
It adds two cache variable definitions to the cmake invocation and also runs
the full test suite after the build step.
ctest --构建和测试 sourceDir buildDir \
--build-generator 忍者 \
--build-options -DCMAKE_BUILD_TYPE=调试 \
-DBUILD_SHARED_LIBS=ON \
--测试命令 ctest -j 4ctest --build-and-test sourceDir buildDir \
--build-generator Ninja \
--build-options -DCMAKE_BUILD_TYPE=Debug \
-DBUILD_SHARED_LIBS=ON \
--test-command ctest -j 4还有一些其他--build-…选项,但上面涵盖了最有用的选项。应该提到的另一个剩余选项是
--test-timeout,它对测试命令在强制终止之前允许运行的时间设置了时间限制(以秒为单位)。
There are a few other --build-… options, but the above covers the most
useful ones. The other remaining option that should be mentioned is
--test-timeout, which places a time limit (in seconds) on how long the test
command is allowed to run before it is forced to terminate.
ctest使用单个命令控制整个管道是否比显式调用每个阶段所需的每个工具更好或更差,取决于具体情况
。上面的最后一个示例可以在 Unix 上使用以下等效命令序列轻松完成:
It is situation-dependent whether controlling the whole pipeline using a single
ctest command is better or worse than invoking each of the tools needed for
each stage explicitly. The last example above could just as easily be done with
the following equivalent sequence of commands on Unix:
mkdir -p 构建目录
cd 构建目录
cmake -G 忍者 \
-DCMAKE_BUILD_TYPE=调试\
-DBUILD_SHARED_LIBS=ON \
源目录
cmake --build 。--目标干净
cmake --build 。
ctest-j 4mkdir -p buildDir
cd buildDir
cmake -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_SHARED_LIBS=ON \
sourceDir
cmake --build . --target clean
cmake --build .
ctest -j 4单独调用每个工具允许它们使用全套选项运行,而该ctest --build-and-test方法控制构建阶段的能力非常有限。
Invoking each tool individually allows them to be run with the full set
of options, whereas the ctest --build-and-test approach has only a
very limited ability to control the build stage.
构建和测试模式特别方便的一种情况是项目需要在一侧执行完整的配置-构建-测试周期,与主构建分开。由于整个周期可以通过单个ctest调用来控制,因此它可以用作对COMMAND的调用的一部分
add_test(),使得将基本 CMake 项目添加到主项目的测试套件的过程相对简单。CMake 本身正是ctest
以这种方式在自己的测试套件中广泛使用构建和测试模式。
One situation where build and test mode is particularly convenient is where a
project needs to perform a complete configure-build-test cycle off to the side,
separate from the main build. Since the whole cycle can be controlled by a
single ctest invocation, it can be used as the COMMAND part of a call to
add_test(), making the process of adding a basic CMake project to the main
project’s test suite relatively straightforward. CMake itself uses the ctest
build and test mode extensively in its own test suite in exactly this manner.
以下示例展示了如何使用单独的构建来测试主项目构建的库提供的 API:
The following example shows how a separate build can be used to test the API provided by a library built by the main project:
add_library(Decoder foo.c bar.c)
add_test(NAME Decoder.api
COMMAND ${CMAKE_CTEST_COMMAND}
--build-and-test ${CMAKE_CURRENT_LIST_DIR}/test_api
${CMAKE_CURRENT_BINARY_DIR}/test_api
--build-generator ${CMAKE_GENERATOR}
--build-options -DDECODER_LIB=$<TARGET_FILE:Decoder>
--test-command ${CMAKE_CTEST_COMMAND}
)add_library(Decoder foo.c bar.c)
add_test(NAME Decoder.api
COMMAND ${CMAKE_CTEST_COMMAND}
--build-and-test ${CMAKE_CURRENT_LIST_DIR}/test_api
${CMAKE_CURRENT_BINARY_DIR}/test_api
--build-generator ${CMAKE_GENERATOR}
--build-options -DDECODER_LIB=$<TARGET_FILE:Decoder>
--test-command ${CMAKE_CTEST_COMMAND}
)
源test_api目录将包含自己的CMakeLists.txt文件,其唯一目的是配置链接到解码器库的构建,其绝对路径在变量中设置DECODER_LIB(这只是将库位置传递给测试的几种方法之一)项目)。
The test_api source directory would contain its own CMakeLists.txt file
whose sole purpose is to configure a build that links against the Decoder
library, the absolute path to which is set in the DECODER_LIB variable (this
is just one of a few ways to pass the library location to the test project).
此类测试的一个有趣之处在于,它还可以用于验证特定的测试项目是否未构建,或验证配置是否因特定的致命错误(例如缺少符号)而失败。这种预期的致命构建错误无法在主项目中进行测试,因为它会导致主项目的构建失败。将测试构建分离到一边意味着它可以失败而不影响主构建,并且测试可以使用适当的
FAIL_REGULAR_EXPRESSION或非零返回代码来验证失败。
An interesting thing about this sort of test is that it can also be used to
verify that a particular test project does not build or to verify that
configuring fails with a particular fatal error (e.g. a missing symbol).
Such expected fatal build errors cannot be tested in the main project, since it
would cause the main project’s build to fail.
Separating the test build off to the side means it can fail without affecting
the main build and the test can verify the failure with an appropriate
FAIL_REGULAR_EXPRESSION or a non-zero return code.
此类测试有用的另一个场景是测试主项目创建的代码生成器的输出。测试装置可用于设置一对测试,一个用于生成代码,另一个用于使用代码执行测试构建。如果代码生成器创建
cmake通常读取的文件(例如CMakeLists.txt文件),这尤其有用。例如:
Another scenario where such tests can be helpful is to test the output of a
code generator created by the main project. Test fixtures can be used to set up
a pair of tests, one to generate the code and the other to perform a test build
with it. This is particularly helpful if the code generator creates files that
cmake would normally read, such as CMakeLists.txt files. For example:
add_executable(CodeGen generator.cpp)
add_test(NAME GenerateCode COMMAND CodeGen)
add_test(NAME BuildGeneratedCode
COMMAND ${CMAKE_CTEST_COMMAND}
--build-and-test ${CMAKE_CURRENT_LIST_DIR}/test_gen
${CMAKE_CURRENT_BINARY_DIR}/test_gen
--build-generator ${CMAKE_GENERATOR}
--test-command ${CMAKE_CTEST_COMMAND}
)
set_tests_properties(GenerateCode
PROPERTIES FIXTURES_SETUP Generator
)
set_tests_properties(BuildGeneratedCode
PROPERTIES FIXTURES_REQUIRED Generator
)add_executable(CodeGen generator.cpp)
add_test(NAME GenerateCode COMMAND CodeGen)
add_test(NAME BuildGeneratedCode
COMMAND ${CMAKE_CTEST_COMMAND}
--build-and-test ${CMAKE_CURRENT_LIST_DIR}/test_gen
${CMAKE_CURRENT_BINARY_DIR}/test_gen
--build-generator ${CMAKE_GENERATOR}
--test-command ${CMAKE_CTEST_COMMAND}
)
set_tests_properties(GenerateCode
PROPERTIES FIXTURES_SETUP Generator
)
set_tests_properties(BuildGeneratedCode
PROPERTIES FIXTURES_REQUIRED Generator
)
构建和测试模式还可以用于验证 CMake 实用程序脚本,方法是将它们包含在小型测试项目中并根据需要调用其功能。实际上,这提供了一种相当方便的方法来实现 CMake 脚本的单元测试,从而避免将此类测试放入主项目的配置阶段。
Build and test mode could also be used to verify CMake utility scripts by including them in a small test project and invoking its functionality as appropriate. In effect, this provides a fairly convenient way to implement unit testing of CMake scripts that avoids having to put such tests into the configure stage of the main project.
虽然构建和测试模式对于上述情况当然很有用,但它缺乏完全脚本化运行的灵活性,在这种情况下,每个命令都可以使用完整的选项集。下一节介绍另一种调用方式ctest,它提供对整个管道更强大的处理,包括一些有用的附加报告功能。
While build and test mode is certainly useful for cases like those mentioned
above, it lacks the flexibility of a fully scripted run where the full set of
options are available for each individual command. The next section introduces
an alternative way of invoking ctest which offers more powerful handling of
the entire pipeline, including some useful additional reporting capabilities.
CTest 与另一个名为 CDash 的产品有着悠久的历史和密切的关系,CDash 也是由 CMake 和 CTest 背后的同一家公司开发的。CDash 是一个基于 Web 的仪表板,它收集由ctest. 它收集管道每个阶段的警告和错误,并显示每个阶段的摘要,并能够单击查看每个单独的警告或错误。过去管道的历史记录可以观察一段时间内的趋势并比较运行情况。
CTest has a long history and close relationship with another product called
CDash, which is also developed by the same company behind CMake and CTest.
CDash is a web-based dashboard which collects results from a software build and
test pipeline driven by ctest. It collects warnings and errors from each
stage of the pipeline and shows per-stage summaries with the ability to click
through to each individual warning or error. A history of past pipelines allows
trends to be observed over time and to compare runs.
CMake 本身有自己相当广泛的仪表板,可以跟踪夜间构建、与合并请求相关的构建等。花几分钟探索示例仪表板将有助于理解本节中涵盖的材料:
CMake itself has its own fairly extensive dashboard which tracks nightly builds, builds associated with merge requests and so on. A few minutes exploring a sample dashboard will be helpful in understanding the material covered in this section:
https://open.cdash.org/index.php?project=CMake
https://open.cdash.org/index.php?project=CMake
三个重要概念将 CTest 和 CDash 执行管道和报告结果的方式联系在一起:步骤(有时也称为操作)、模型 (有时也称为模式)和组(以前在 CMake 3.15 及更早版本中称为轨道)。步骤是管道执行的操作序列。按照通常调用顺序排列的主要已定义操作集是:
Three important concepts tie together how CTest and CDash execute pipelines and report results: steps (sometimes also referred to as actions), models (also sometimes called modes) and groups (previously called tracks with CMake 3.15 and earlier). Steps are the sequence of actions that a pipeline performs. The main set of defined actions in the order they would normally be invoked is:
并非所有操作都必须执行,某些操作可能不受支持或不需要运行。宽松地说,CDash 仪表板中的每一行对应一个管道,通常会显示所采取的每个操作的摘要(提交哈希、警告、错误、失败总数等)。
Not all actions have to be executed, some may not be supported or do not need to be run. Loosely speaking, each row in the CDash dashboard corresponds to a single pipeline and will typically show a summary of each action taken (a commit hash, a total of warnings, errors, failures, etc.).
每个管道必须与一个模型关联,该模型用于定义某些行为,例如在特定步骤失败后是否继续执行后续步骤。当没有请求特定操作时,该模型还提供一组默认操作。支持的型号有:
Each pipeline must be associated with a model, which is used to define certain behaviors, such as whether or not to continue with later steps after a particular step fails. The model also provides a default set of actions when no specific action is requested. The supported models are:
组控制管道结果将显示在仪表板结果中的哪个组下。组名称可以是项目或开发人员希望使用的任何名称,但如果未指定组,它将设置为与模型相同。这导致了一个常见的误解,即模型控制仪表板中的分组,但执行此操作的是组。Coverage和 MemCheck操作是一种特殊情况,它们实际上忽略该组,并且它们的仪表板结果显示在它们自己的专用组中(分别为Coverage 和Dynamic Analysis)。
The group controls which group the pipeline results will be shown under in the dashboard results. Group names can be anything the project or developer wishes to use, but if no group is specified, it will be set to the same as the model. This has led to a common misunderstanding that the model controls the grouping in the dashboard, but it is the group that does this. The Coverage and MemCheck actions are a special case, they effectively ignore the group and their dashboard results are shown in their own dedicated groups (Coverage and Dynamic Analysis respectively).
对于具有必要配置文件(在下一节中介绍)的项目,可以使用以下形式的命令调用整个管道或单个步骤ctest:
For a project with the necessary configuration files in place (covered in the
next section), entire pipelines or individual steps can be invoked using the
following form of the ctest command:
ctest [-M 模型] [-T 动作] [--group 组] ...
ctest [-M Model] [-T Action] [--group Group] ...
如果使用 CMake 3.16 或更早版本,则--track Track必须使用而不是
--group Group.
If using CMake 3.16 or earlier, --track Track must be used rather than
--group Group.
必须至少指定模型和操作之一或两者。为了方便起见,-M和-T选项可以组合成一个-D
选项,如下所示:
At least one or both of the Model and Action must be specified. As a
convenience, the -M and -T options can be combined into a single -D
option like so:
ctest -D 模型[操作] [--group 组] ...
ctest -D Model[Action] [--group Group] ...
参数-D可以省略操作或将其附加到Model。有效参数的示例包括Continuous、NightlyConfigure、ExperimentalBuild
等。如果需要,可以多次指定-T和选项,以在一次调用中列出多个步骤。-Dctest
Arguments to -D can omit the action or append it to the Model. Examples of
valid arguments include Continuous, NightlyConfigure, ExperimentalBuild
and so on. The -T and -D options can be specified multiple times to list
multiple steps in the one ctest invocation if desired.
使用默认步骤集并在默认组Nightly下报告其结果的夜间运行可以简单地调用为:
A nightly run using the default set of steps and reporting its results under the default group Nightly is trivially invoked as:
ctest -M 每晚
ctest -M Nightly
对于同样的事情,但在一个名为Nightly Master 的小组下报告了结果:
For the same thing but with results reported under a group called Nightly Master:
ctest -M Nightly --group“夜间大师”
ctest -M Nightly --group "Nightly Master"
考虑一个自定义实验管道,仅包含配置、
构建和测试步骤,结果分组在简单测试下。这需要显式指定步骤集,因为它与为实验模型定义的默认操作集不同(没有执行覆盖步骤)。这可以通过每次调用一个步骤的一系列调用来完成,也可以使用一个命令行上的ctest
多个选项将它们全部列出。-T显示两种形式以供比较:
Consider a custom Experimental pipeline consisting of just Configure,
Build and Test steps with results grouped under Simple Tests. This
requires the set of steps to be explicitly specified, since it differs from the
default set of actions defined for an Experimental model (no Coverage step
is being executed). This can be done as either a sequence of ctest
invocations with one step per invocation, or they could all be listed together
using multiple -T options on the one command line. Both forms are shown for
comparison:
ctest -T Start -M Experimental --group“简单测试” ctest -T 配置 ctest -T 构建 ctest -T 检验 ctest -T 提交
ctest -T Start -M Experimental --group "Simple Tests" ctest -T Configure ctest -T Build ctest -T Test ctest -T Submit
ctest -M Experimental --group“简单测试”\
-T 开始 -T 配置 -T 构建 -T 测试 -T 提交ctest -M Experimental --group "Simple Tests" \
-T Start -T Configure -T Build -T Test -T Submit第一步应该是“开始”操作,用于初始化管道详细信息并记录后续步骤将使用的模型和组名称。如果将每个操作拆分为自己单独的调用,则不需要为任何后续步骤重复这些详细信息ctest。最后一步是提交操作,假设目标是将最终结果集提交到仪表板。
The first step should be a Start action, which is used to initialize the
pipeline details and to record the model and group names that later steps will
use. These details do not need to be repeated for any of the later steps if
splitting each action out to its own separate ctest invocation. The last step
would be a Submit action, assuming the goal is to submit the final set of
results to a dashboard.
上述所有输出都收集在调用Testing目录下的子目录下ctest。“开始”操作写出一个名为 的文件TAG,其中至少包含两行,第一行是表单中运行开始的日期时间YYYYMMDD-hhmm,第二行是组名称。CMake 3.12 添加了包含模型名称的第三行。
All output from the above is collected under a Testing subdirectory below the
directory in which ctest is invoked. The Start action writes out a file
named TAG which contains at least two lines, the first being a date-time for
the start of the run in the form YYYYMMDD-hhmm and the second being the group
name. CMake 3.12 adds a third line containing the model name.
在执行“开始”操作后的每个步骤时,它将创建自己的输出文件 atTesting/YYYYMMDD-hhmm/<Action>.xml和一个日志文件 at
(在MemCheckTesting/Temporary/Last<Action>_YYYYMMDD-hhmm.log步骤的情况下
,该部分将而不是
在这些文件名中)。提交操作收集 XML 输出文件和一些日志文件,并将它们提交到指定的仪表板。<Action>DynamicAnalysisMemCheck
As each step after the Start action is executed, it will create its own
output file at Testing/YYYYMMDD-hhmm/<Action>.xml and a log file at
Testing/Temporary/Last<Action>_YYYYMMDD-hhmm.log (in the case of the
MemCheck step, the <Action> part will be DynamicAnalysis rather than
MemCheck in these file names). The Submit action collects the XML output
files and some of the log files and submits them to the nominated dashboard.
要将构建注释附加到整个管道,请在“提交”-A步骤中使用或--add-notes
选项来指定要上传的文件名,如果要添加多个文件,则用分号分隔。这可能是记录有关特定管道的额外详细信息的有用方法,例如来自启动运行的持续集成系统的信息。
To attach a build note to the whole pipeline, use the -A or --add-notes
option with the Submit step to specify the file names to upload, separated
by semi-colons if multiple files are being added. This can be a useful way to
record extra details about that particular pipeline, such as information from
a continuous integration system that initiated the run.
ctest -T 提交 --add-note JobNote.txt
ctest -T Submit --add-note JobNote.txt
还支持一个--extra-submit选项,但它更多地供ctest. 它不是通用的文件上传机制,但经常被错误地认为可以用于此目的。它不应该由开发人员或项目直接使用。
An --extra-submit option is also supported, but it is intended more for
internal use by ctest.
It is not a general file upload mechanism, but is often mistakenly assumed to
serve that purpose.
It should not be used by developers or projects directly.
虽然上述功能主要用于与 CDash 集成,但它也可以用于其他场景。例如,Jenkins CI 系统有一个插件,允许其读取测试操作的Test.xml输出文件并以与 CDash 类似的方式记录测试结果。它可以仅使用测试操作作为仪表板运行来调用,而不是以ctest
普通方式运行
。然后,Jenkins 插件只需要被告知在哪里可以找到该
文件,并且它就能够读取测试结果。以这种方式使用时,甚至可以省略“开始”操作,因为如果在没有任何先前“开始”操作的情况下执行其他步骤之一,则将默默地执行与实验模型的“开始”操作等效的操作。项目可能希望在执行此操作之前清除该目录的所有先前内容,以确保 Jenkins 仅获取当前运行的结果。Test.xmlctestTesting
While the above functionality is intended primarily for integration with CDash,
it can also be used for other scenarios too. For example, the Jenkins CI system
has a plugin that allows it to read the Test action’s Test.xml output file
and record test results in a similar way to CDash. Instead of running ctest
in the ordinary way, it can be invoked as a dashboard run with just the Test
action. The Jenkins plugin then only needs to be told where to find the
Test.xml file and it is able to read the test results. When used this way,
even the Start action can be omitted, since ctest will silently perform the
equivalent of a Start action with an Experimental model if one of the other
steps is executed without any prior Start action. Projects may want to clear
any previous contents of the Testing directory before doing so to ensure only
the results of the current run are picked up by Jenkins.
将操作的 XML 输出文件传递到 CDash 以外的工具时,可能需要指示ctest不压缩其捕获的输出。默认情况下,操作的输出被压缩并以 ASCII 编码形式写入 XML 文件,但是可以通过将选项传递
--no-compress-output给来防止这种情况ctest。仅在必要时使用此选项,因为它将导致更大的输出文件。
When passing the XML output file of an action to a tool other than CDash, it
may be necessary to instruct ctest to not compress the output it captures.
By default, the action’s output is compressed and written to the XML file in an
ASCII-encoded form, but this can be be prevented by passing the
--no-compress-output option to ctest. Only use this option if it is
necessary, since it will result in larger output files.
仪表板步骤在没有 CDash 的情况下也能发挥作用的另一种情况是利用对代码覆盖率或内存检查的支持(Valgrind、Purify、各种消毒剂等)。这些仪表板操作可以使调用相关工具和收集结果变得更加容易。有关如何设置和使用这些工具的详细信息,请参阅下一节。
Another situation where dashboard steps can be useful without CDash is to take advantage of the support for code coverage or memory checking (Valgrind, Purify, various sanitizers, etc.). These dashboard actions can make invoking the relevant tool and collecting results easier. See the next section for details on how to setup and use these tools.
为 CDash 集成准备项目主要由CTestCMake 提供的模块来处理。它应该包含project()在顶级
CMakeLists.txt文件中的命令之后不久。这很重要,因为模块在包含它时会将各种文件写入当前构建目录,并且开发人员通常希望能够ctest从顶级构建目录运行。
Preparing a project for CDash integration is mostly handled by a CTest module
provided by CMake.
It should be included soon after the project() command in the top level
CMakeLists.txt file.
This is important because the module writes various files into the current
build directory at the point it is included, and developers typically expect to
be able to run ctest from the top level build directory.
cmake_minimum_required(VERSION 3.0)
project(CDashExample)
# ... set any variables to customize CTest behavior
include(CTest)
# ... Define targets and tests as usualcmake_minimum_required(VERSION 3.0)
project(CDashExample)
# ... set any variables to customize CTest behavior
include(CTest)
# ... Define targets and tests as usual
该CTest模块定义了一个BUILD_TESTING默认为 true 的缓存变量。它用于决定模块是否调用enable_testing(),因此项目不必enable_testing()也进行自己的调用。仅当启用测试时,项目也可以使用此缓存变量来执行某些处理。如果项目有许多测试需要很长时间才能构建,那么这可能是避免在不需要时将它们添加到构建中的有用方法。
The CTest module defines a BUILD_TESTING cache variable which defaults
to true. It is used to decide whether the module calls enable_testing() or
not, so the project does not have to make its own call to enable_testing() as
well. This cache variable can also be used by the project to perform certain
processing only if testing is enabled. If the project has many tests that take
a long time to build, this can be a useful way to avoid adding them to the
build when they are not needed.
cmake_minimum_required(VERSION 3.0)
project(CDashExample)
include(CTest)
# ... define regular targets
if(BUILD_TESTING)
# ... define test targets and add tests
endif()cmake_minimum_required(VERSION 3.0)
project(CDashExample)
include(CTest)
# ... define regular targets
if(BUILD_TESTING)
# ... define test targets and add tests
endif()
该模块定义每个模型和每个
ModelActionCTest组合的构建目标。这些目标使用设置为目标名称的选项来执行,旨在作为一种在 IDE 应用程序中执行整个管道或仅执行一个仪表板操作的便捷方法。如果从命令行工作,这些目标不会比直接调用提供任何真正的优势。ctest-Dctest
The CTest module defines build targets for each Model and for each
ModelAction combination. These targets execute ctest with the -D option
set to the target name and are intended as a convenient way to execute the
whole pipeline or just one dashboard action from within an IDE application. The
targets don’t offer any real advantage over invoking ctest directly if
working from the command line.
该模块执行的更重要的任务CTest是写出DartConfiguration.tcl在构建目录中调用的配置文件。该文件的名称是历史性的,Dart 是 CDash 项目的原始名称。该文件记录基本详细信息,例如源和构建目录位置、正在执行构建的计算机的信息、使用的工具链、各种工具的位置和其他默认值。它还将包含 CDash 服务器的详细信息,但为了做到这一点,项目需要CTestConfig.cmake在源树顶部提供一个包含相关内容的文件。可以从 CDash 本身获取合适的CTestConfig.cmake文件(需要管理员权限),但手动创建一个通常并不困难。适用于所有 CMake 版本的最小示例如下所示:
The more important task performed by the CTest module is to write out a
configuration file called DartConfiguration.tcl in the build directory. The
name of this file is historical, with Dart being the original name of the CDash
project. This file records basic details like the source and build directory
locations, information about the machine on which the build is being performed,
the toolchain used, the location of various tools and other defaults. It will
also contain the details of the CDash server, but in order for it to do so, the
project needs to provide a CTestConfig.cmake file at the top of the source
tree with the relevant contents. A suitable CTestConfig.cmake file can be
obtained from CDash itself (requires administrator privileges), but it is
usually not difficult to create one manually. A minimal example which works for
all CMake versions would look something like this:
# Name used by CDash to refer to the project
set(CTEST_PROJECT_NAME "MyProject")
# Time to use for the start of each day. Used by CDash to
# group results by day, usually set to midnight in the
# local timezone of the CDash server.
set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC")
# Details of the CDash server to submit to
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION
"/submit.php?project=${CTEST_PROJECT_NAME}"
)
set(CTEST_DROP_SITE_CDASH YES)
# Optional, but recommended so that command lines can be
# seen in the CDash logs
set(CTEST_USE_LAUNCHERS YES)# Name used by CDash to refer to the project
set(CTEST_PROJECT_NAME "MyProject")
# Time to use for the start of each day. Used by CDash to
# group results by day, usually set to midnight in the
# local timezone of the CDash server.
set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC")
# Details of the CDash server to submit to
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION
"/submit.php?project=${CTEST_PROJECT_NAME}"
)
set(CTEST_DROP_SITE_CDASH YES)
# Optional, but recommended so that command lines can be
# seen in the CDash logs
set(CTEST_USE_LAUNCHERS YES)
从 CMake 3.14 开始,各种CTEST_DROP_…选项可以替换为单个CTEST_SUBMIT_URL选项。这更简单且更具可读性,因此如果项目的最低 CMake 版本至少为 3.14,则应首选此方法。上面的例子的等价物是:
From CMake 3.14, the various CTEST_DROP_… options can be replaced by a
single CTEST_SUBMIT_URL option.
This is much simpler and more readable, so if the minimum CMake version of the
project is at least 3.14, this should be preferred.
The equivalent for the above example would be:
set(CTEST_SUBMIT_URL
"http://my.cdash.org/submit.php?project=${CTEST_PROJECT_NAME}"
)set(CTEST_SUBMIT_URL
"http://my.cdash.org/submit.php?project=${CTEST_PROJECT_NAME}"
)
该模块DartConfiguration.tcl写出的文件CTest包含仪表板操作的选项。默认情况下,大多数设置为适当的值,但Coverage和
MemCheck步骤具有可能令人感兴趣的选项。这些由 CMake 变量控制,开发人员可以在包含模块CMakeLists.txt之前在 CMake 缓存或文件中操作这些变量。CTest
The DartConfiguration.tcl file written out by the CTest module contains
options for dashboard actions.
Most are set to appropriate values by default, but the Coverage and
MemCheck steps have options that may be of interest.
These are controlled by CMake variables which the developer can manipulate in
the CMake cache or in the CMakeLists.txt file before the CTest module is
included.
假定正在调用Coverage步骤gcov,并且CTest模块将搜索该名称的命令。缓存COVERAGE_COMMAND变量保存该搜索的结果,但开发人员可以根据需要对其进行修改。第二个缓存变量COVERAGE_EXTRA_FLAGS用于保存应立即跟随的选项COVERAGE_COMMAND,因此开发人员能够控制所使用的命令和传递给它的选项。
The Coverage step is assumed to be invoking gcov and the CTest module
will search for a command by that name. The COVERAGE_COMMAND cache variable
holds the result of that search, but it can be modified by the developer if
needed. A second cache variable COVERAGE_EXTRA_FLAGS is used to hold the
options that should immediately follow the COVERAGE_COMMAND, so the developer
has the ability to control both the command used and the options passed to it.
MemCheck步骤更有趣。支持许多不同的内存检查器,包括 Valgrind、Purify、BoundsChecker、Dr Memory(使用 CMake 3.17 或更高版本)、Cuda Sanitizer(使用 CMake 3.19 或更高版本)和各种其他消毒剂。对于前五个,可以通过设置
MEMORYCHECK_COMMAND相关可执行文件的位置来选择它们。ctest
然后将从可执行文件名称中识别检查器。对于 Valgrind,
VALGRIND_COMMAND_OPTIONS还可以设置变量来覆盖给定的选项valgrind。Dr Memory 具有与变量类似的功能
DRMEMORY_COMMAND_OPTIONS。要使用其中一种消毒剂,请设置
MEMORYCHECK_TYPE为以下字符串之一(MEMORYCHECK_COMMAND
然后将被忽略):
The MemCheck step is more interesting. A number of different memory checkers
are supported, including Valgrind, Purify, BoundsChecker, Dr Memory (with CMake
3.17 or later), Cuda Sanitizer (with CMake 3.19 or later) and various
other sanitizers.
For the first five, they can be selected by setting
MEMORYCHECK_COMMAND to the location of the relevant executable. ctest
will then identify the checker from the executable name. For Valgrind, the
VALGRIND_COMMAND_OPTIONS variable can also be set to override the options
given to valgrind itself. Dr Memory has a similar capability with the
DRMEMORY_COMMAND_OPTIONS variable. To use one of the sanitizers, set
MEMORYCHECK_TYPE to one of the following strings (MEMORYCHECK_COMMAND
will then be ignored):
AddressSanitizer
AddressSanitizer
LeakSanitizer
LeakSanitizer
MemorySanitizer
MemorySanitizer
ThreadSanitizer
ThreadSanitizer
UndefinedBehaviorSanitizer
UndefinedBehaviorSanitizer
ctest然后将正常启动测试可执行文件,但设置相关环境变量以启用所请求的清理程序。请注意,清理程序需要使用相关的编译器和链接器标志(通常是-fsanitize=XXX,也许是-fno-omit-frame-pointer)进行构建。有关相关标志以及各种消毒剂的作用的更多详细信息,请参阅 Clang 或 GCC 文档。
ctest will then launch test executables as normal but with the relevant
environment variables set to enable the requested sanitizer.
Note that sanitizers require building with the relevant compiler and linker
flags (typically -fsanitize=XXX and perhaps -fno-omit-frame-pointer).
For further details on the relevant flags and what the various sanitizers do,
consult the Clang or GCC documentation.
上述细节足以执行各种仪表板操作并将结果提交到 CDash 服务器,但存在先有鸡还是先有蛋的问题。需要已执行更新和配置步骤才能获取该文件。因此,无法捕获这两个步骤的详细信息,或者在配置步骤的情况下,第一次运行的输出会丢失,只能通过在已配置的构建目录中重新运行 CMake 来获取输出。尽管如此,所有其他步骤都会捕获其输出,这在某些情况下可能就足够了。DartConfiguration.tclcmake
The above details are enough to be able to perform various dashboard actions
and submit results to a CDash server, but there is a chicken-and-egg problem.
The Update and Configure steps need to have already been performed to
obtain the DartConfiguration.tcl file. Therefore, details of those two steps
cannot be captured, or in the case of the Configure step, the output from the
first cmake run are lost and one can only get the output from re-running
CMake in an already-configured build directory. Nevertheless, all the other
steps will have their output captured and that may be enough in some
situations.
例如,当使用像 Gitlab CI 或 Jenkins 这样的持续集成系统时,源代码树的初始克隆或更新可以由 CI 系统本身来处理。可以执行初始cmake运行,然后可以将其余步骤作为仪表板操作运行。最终结果可以提交到 CDash 服务器,由 CI 系统直接读取,或者两者都可以。
For example, when using a continuous integration system like Gitlab CI or
Jenkins, the initial clone or update of the source tree can be handled by
the CI system itself.
An initial cmake run can be performed and then the rest of the steps can be
run as dashboard actions.
The final results can be submitted to a CDash server, read directly by the CI
system or possibly both.
要捕获完整的管道,包括源树的初始克隆或更新以及第一个配置步骤,必须编写自定义ctest脚本来定义所需的设置详细信息并调用相关ctest函数。这可能是一个复杂的过程,如果已经使用另一个 CI 系统,通常没有必要。如果不需要捕获克隆/更新步骤,则自定义脚本的复杂性就会降低。当以这种方式使用时,ctest使用-S选项和要执行的脚本的名称来调用。
To capture a complete pipeline, including the initial clone or
update of a source tree and first configure step, one has to write a
custom ctest script to define the required setup details and call the
relevant ctest functions. This can be an involved process and isn’t
typically necessary if already using another CI system. If the clone/update
step doesn’t need to be captured, then the complexity of the custom script is
reduced. When used this way, ctest is invoked with the -S option and the
name of the script to execute.
下面演示了一个相当简单的示例:
The following demonstrates a fairly straightforward example:
ctest -S MyCustomCTestJob.cmake
ctest -S MyCustomCTestJob.cmake
# Re-use CDash server details we already have
include(${CTEST_SCRIPT_DIRECTORY}/CTestConfig.cmake)
# Basic information every run should set, values here are
# just examples
site_name(CTEST_SITE)
set(CTEST_BUILD_NAME ${CMAKE_HOST_SYSTEM_NAME})
set(CTEST_SOURCE_DIRECTORY
"${CTEST_SCRIPT_DIRECTORY}"
)
set(CTEST_BINARY_DIRECTORY
"${CTEST_SCRIPT_DIRECTORY}/build"
)
set(CTEST_CMAKE_GENERATOR Ninja)
set(CTEST_CONFIGURATION_TYPE RelWithDebInfo)
# Dashboard actions to execute, always clearing the
# build directory first
ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY})
ctest_start(Experimental)
ctest_configure()
ctest_build()
ctest_test()
ctest_submit()# Re-use CDash server details we already have
include(${CTEST_SCRIPT_DIRECTORY}/CTestConfig.cmake)
# Basic information every run should set, values here are
# just examples
site_name(CTEST_SITE)
set(CTEST_BUILD_NAME ${CMAKE_HOST_SYSTEM_NAME})
set(CTEST_SOURCE_DIRECTORY
"${CTEST_SCRIPT_DIRECTORY}"
)
set(CTEST_BINARY_DIRECTORY
"${CTEST_SCRIPT_DIRECTORY}/build"
)
set(CTEST_CMAKE_GENERATOR Ninja)
set(CTEST_CONFIGURATION_TYPE RelWithDebInfo)
# Dashboard actions to execute, always clearing the
# build directory first
ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY})
ctest_start(Experimental)
ctest_configure()
ctest_build()
ctest_test()
ctest_submit()
以下更有趣的示例显示了自定义脚本如何允许定义更灵活的管道行为:
The following more interesting example shows how custom scripts allow more flexible pipeline behavior to be defined:
include(${CTEST_SCRIPT_DIRECTORY}/CTestConfig.cmake)
site_name(CTEST_SITE)
set(CTEST_BUILD_NAME
"${CMAKE_HOST_SYSTEM_NAME}-ASan"
)
set(CTEST_SOURCE_DIRECTORY
"${CTEST_SCRIPT_DIRECTORY}"
)
set(CTEST_BINARY_DIRECTORY
"${CTEST_SCRIPT_DIRECTORY}/build"
)
set(CTEST_CMAKE_GENERATOR Ninja)
set(CTEST_CONFIGURATION_TYPE RelWithDebInfo)
set(CTEST_MEMORYCHECK_TYPE AddressSanitizer)
set(flags "-fsanitize=address -fno-omit-frame-pointer")
set(configureOpts
"-DCMAKE_CXX_FLAGS_INIT=${flags}"
"-DCMAKE_EXE_LINKER_FLAGS_INIT=${flags}"
)
ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY})
ctest_start(Experimental GROUP Sanitizers)
ctest_configure(OPTIONS "${configureOpts}")
ctest_submit(PARTS Start Configure)
ctest_build()
ctest_submit(PARTS Build)
ctest_memcheck()
ctest_submit(PARTS MemCheck)
ctest_upload(FILES
${CTEST_BINARY_DIRECTORY}/mytest.log
${CTEST_BINARY_DIRECTORY}/anotherFile.txt
)
ctest_submit(PARTS Upload Submit)
if(NOT CMAKE_VERSION VERSION_LESS "3.14")
ctest_submit(PARTS Done)
endif()include(${CTEST_SCRIPT_DIRECTORY}/CTestConfig.cmake)
site_name(CTEST_SITE)
set(CTEST_BUILD_NAME
"${CMAKE_HOST_SYSTEM_NAME}-ASan"
)
set(CTEST_SOURCE_DIRECTORY
"${CTEST_SCRIPT_DIRECTORY}"
)
set(CTEST_BINARY_DIRECTORY
"${CTEST_SCRIPT_DIRECTORY}/build"
)
set(CTEST_CMAKE_GENERATOR Ninja)
set(CTEST_CONFIGURATION_TYPE RelWithDebInfo)
set(CTEST_MEMORYCHECK_TYPE AddressSanitizer)
set(flags "-fsanitize=address -fno-omit-frame-pointer")
set(configureOpts
"-DCMAKE_CXX_FLAGS_INIT=${flags}"
"-DCMAKE_EXE_LINKER_FLAGS_INIT=${flags}"
)
ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY})
ctest_start(Experimental GROUP Sanitizers)
ctest_configure(OPTIONS "${configureOpts}")
ctest_submit(PARTS Start Configure)
ctest_build()
ctest_submit(PARTS Build)
ctest_memcheck()
ctest_submit(PARTS MemCheck)
ctest_upload(FILES
${CTEST_BINARY_DIRECTORY}/mytest.log
${CTEST_BINARY_DIRECTORY}/anotherFile.txt
)
ctest_submit(PARTS Upload Submit)
if(NOT CMAKE_VERSION VERSION_LESS "3.14")
ctest_submit(PARTS Done)
endif()
在这个更具生产质量的示例中,不是等到运行最后才将结果提交到仪表板,而是在每个步骤之后逐步提交结果(如果某些步骤需要很长时间,这很有用)。可执行文件是使用地址清理程序支持构建的,并且运行地址清理程序检查而不是常规测试。最后还会上传一些额外的文件。
In this more production-quality example, rather than waiting to the very end of the run before submitting results to the dashboard, results are submitted progressively after each step (useful if some steps take a long time). The executables are built with address sanitizer support and the address sanitizer check is run instead of regular testing. Some extra files are also uploaded at the end.
Done部分仅在CMake 3.14 中添加,用于告诉 CDash 作业已完成。最新版本的 CDash 使用它来报告更可靠的作业总持续时间。早期的 CDash 版本将简单地忽略它。当不按部分提交时,“完成”部分将作为“提交”操作的一部分自动处理,或作为未指定部分的调用的一部分ctest_submit()。
The Done part was only added in CMake 3.14 and is used to tell CDash that the
job is complete.
It is used by recent versions of CDash to enable them to report a more reliable
total duration for the job.
Earlier CDash versions will simply ignore it.
When not submitting by parts, the Done part is handled automatically as part
of the Submit action or a call to ctest_submit() with no parts specified.
ctest_…CMake 文档中详细介绍了各个命令,CTest以及可用于自定义每个步骤或以各种方式影响处理的 CMake 变量。上面应该是一个很好的基本脚本,可用于试验不同的参数和变量。
Each of the various ctest_… commands is detailed in the CMake
documentation, along with CTest and CMake variables that can be used to
customize each step or affect the processing in various ways. The above should
be a good base script that can be used to experiment with the different
parameters and variables.
创建还处理克隆/更新项目的脚本会增加更多的复杂性。项目通常有自己特殊的方法来做到这一点,他们通常需要决定如何安排诸如夜间构建和连续构建之类的事情。支持合并请求的自动构建等功能在很大程度上取决于托管项目的存储库的功能。对于那些有兴趣探索这条道路的人,推荐的入门方法是找到一个使用类似存储库托管安排的项目并将其用作指南。一些项目在其存储库中包含自定义脚本,以便于访问(Kitware 中的许多项目都这样做,并且脚本已被很好地记录下来)。
Creating a script that also handles cloning/updating the project adds more complexity. Projects often have their own special ways of doing this and they typically need to decide how things like Nightly and Continuous builds should be scheduled. Supporting things like automated builds for merge requests will depend heavily on the capabilities of the repository hosting the project. For those interested in exploring this path, a recommended way to get started is to find a project using a similar repository hosting arrangement and use it as a guide. Some projects include the custom script in their repository for ease of access (many projects from Kitware do this and the scripts have been documented reasonably well).
上面的示例简要展示了如何将文件上传合并到自定义 CTest 脚本中。该ctest_upload()命令提供了记录文件以与构建结果一起上传的基本机制。上传是作为后续调用的一部分执行的ctest_submit()。然而,有时,文件上传应该与特定测试而不是整个脚本运行相关联。为此,CMake 提供了ATTACHED_FILES和
ATTACHED_FILES_ON_FAILtest 属性。两者都包含要上传的文件列表并与该特定测试相关联,唯一的区别是后者包含仅在测试失败时才上传的文件。这是记录有关失败的附加信息以便进一步调查的非常有用的方法。
The above example briefly showed how file uploads can be incorporated into a
custom CTest script.
The ctest_upload() command provides a basic mechanism for recording files to
upload with the build results.
The upload is executed as part of a subsequent call to ctest_submit().
Sometimes, however, file uploads should be associated with a particular test
rather than the whole scripted run.
For this, CMake provides the ATTACHED_FILES and
ATTACHED_FILES_ON_FAIL test properties.
Both hold a list of files to be uploaded and associated with that particular
test, the only difference is that the latter contains files that are only
uploaded if the test fails.
This is a very useful way to record additional information about the failure to
allow further investigation.
add_executable(CodeGen ...)
add_test(NAME GenerateFile COMMAND CodeGen)
set_tests_properties(GenerateFile PROPERTIES
ATTACHED_FILES_ON_FAIL
${CMAKE_CURRENT_BINARY_DIR}/generated.c
${CMAKE_CURRENT_BINARY_DIR}/generated.h
)add_executable(CodeGen ...)
add_test(NAME GenerateFile COMMAND CodeGen)
set_tests_properties(GenerateFile PROPERTIES
ATTACHED_FILES_ON_FAIL
${CMAKE_CURRENT_BINARY_DIR}/generated.c
${CMAKE_CURRENT_BINARY_DIR}/generated.h
)
测试还可以记录单个测量值,该值将在 CDash 中记录和跟踪。测量通常采用 的形式key=value,尽管=value可以省略该部分以使用假定的默认值 1。测量被记录为测试属性,如下所示:
Tests can also record a single measurement value which will be recorded and
tracked in CDash.
A measurement generally has the form key=value, although the =value part
can be omitted to use an assumed default value of 1.
The measurement is recorded as a test property like so:
set_tests_properties(PerfRun PROPERTIES
MEASUREMENT mySpeed=${someValue}
)set_tests_properties(PerfRun PROPERTIES
MEASUREMENT mySpeed=${someValue}
)
由于测量值必须在测试运行之前定义,因此其用处有限。更有用的是一个长期以来一直受到支持的功能,但自 CMake 3.21 以来才进行记录,其中测量可以以类似于 HTML 标签的形式嵌入到测试输出中。
ctest扫描这些测量的测试输出,提取相关数据并将其作为测试结果的一部分上传到 CDash。然后,这些测量结果将显示在测试详细信息页面顶部附近的结果表中。最简单的测量类型由以下形式定义:
Because the measurement value has to be defined before the test is even run,
this has limited usefulness.
Much more useful is a feature which has long been supported, but only been
documented since CMake 3.21, where measurements can be embedded in the test
output in a form similar to HTML tags.
ctest scans the test output for these measurements, extracts the relevant
data and uploads it to CDash as part of the test results.
These measurements are then displayed in a result table near the top of the
test details page.
The simplest type of measurement is defined by the following form:
<DartMeasurement name="key" type="someType">value</DartMeasurement><DartMeasurement name="key" type="someType">value</DartMeasurement>
该name属性将用作结果表中测量的标签,该type属性通常类似于
text/string, text/link(对于 URL)或numeric/double。是value对测量有意义的任何文本或数字内容。对于数值,CDash 提供了一种工具来绘制最近测试运行中每次测量的历史记录,这对于发现行为随时间的变化非常有用。
The name attribute will be used as the label for the measurement in the
results table and the type attribute will typically be something like
text/string, text/link (for URLs) or numeric/double.
The value is whatever text or numerical content makes sense for the
measurement.
For numerical values, CDash provides a facility to plot the history of each
measurement across recent test runs, which is very useful for spotting changes
in behavior over time.
另一种形式可用于嵌入文件而不是特定值:
Another form can be used to embed a file rather than a specific value:
<DartMeasurementFile name="key" type="someType">filePath</DartMeasurementFile><DartMeasurementFile name="key" type="someType">filePath</DartMeasurementFile>
第二种形式对于上传图像最有用,其中type
属性类似于image/png或image/jpeg。应该
filePath是要上传的文件的绝对路径。
This second form is most useful for uploading images, where the type
attribute would be something like image/png or image/jpeg. The
filePath should be the absolute path to the file to be uploaded.
当涉及到图像时,CDash 会识别一些特殊的测量名称。这些可用于帮助比较预期图像和实际图像,CDash 甚至提供有用的交互式 UI 元素来进行重叠比较。公认的name属性及其含义包括:
CDash recognizes a few special measurement names when it comes to images. These
can be used to help compare expected and actual images, with CDash even
providing a useful interactive UI element for overlapped comparisons. The
recognized name attributes and their meanings include:
TestImage
TestImage
ValidImage
ValidImage
TestImage,但不一定要求具有相同的图像格式。它将仅包含在交互式图像中。BaselineImage也可以用作 thename并且与 含义相同ValidImage。
TestImage, but is not necessarily
required to be of the same image format. It will be included in the interactive
image only. BaselineImage can also be used as the name and it means the
same thing as ValidImage.
DifferenceImage2
DifferenceImage2
可以上传上述以外的名称和图像以外的类型,但 CMake 3.20 及更早版本中的错误可能会导致上传不可靠。为了获得最佳结果,请尽可能使用 3.21 或更高版本。type当设置为 .CMake 3.21 时,CMake 3.21 还可以更好地处理非图像文件file。然后该文件将作为附件上传,就像ATTACHED_FILES
测试属性一样。对于任何其他type,它将被视为命名测量,并且可能无法在 CDash 中正确显示。
Names other than the above and types other than images can be uploaded, but
bugs in CMake 3.20 and earlier may make that unreliable.
For best results, use 3.21 or later if possible.
CMake 3.21 also provides better handling for non-image files when the type is
set to file.
The file will then be uploaded as an attachment, just like the ATTACHED_FILES
test property would.
With any other type, it will be treated as a named measurement instead and
may not display as appropriately in CDash.
CMake 3.21 还添加了测试输出覆盖 CDash 中测试详细信息字段的功能。默认内容通常说明测试是否完成以及测试结果是什么,但测试可以使用此工具来提供更具体的信息。但它仍然应该相对较短,最好不超过一行。
CMake 3.21 also added the ability for the test output to override the test’s details field in CDash. The default contents normally state whether the test completed and what the test result was, but the test can use this facility to provide more specific information. It should still be relatively short though, ideally no more than one line.
<CTestDetails>Replacement test details go here</CTestDetails><CTestDetails>Replacement test details go here</CTestDetails>
虽然 CDash 对于收集和跟踪测试结果非常有效,但许多项目使用其他工具来实现此目的。为了支持这些工作流程,CMake 3.21 添加了以广泛支持的 JUnit XML 格式提供测试结果的功能。JUnit XML 文件通常能够导入 CI 工具和各种测试报告软件中。
While CDash can be very effective for collecting and tracking test results, many projects use other tools for such purposes. To support these workflows, CMake 3.21 added the ability to provide test results in the widely supported JUnit XML format. JUnit XML files are often able to be imported into CI tools and various test reporting software.
对于非仪表板运行,可以通过向ctest命令行添加选项来生成 JUnit 结果文件,指定要将结果写入的文件名:
For non-dashboard runs, a JUnit result file can be generated by adding an
option to the ctest command line, specifying the file name to write the
results to:
ctest --output-junit /path/to/resultFile.xml ...
ctest --output-junit /path/to/resultFile.xml ...
对于仪表板运行,该命令的附加参数ctest_test()可以实现类似的效果:
For dashboard runs, an additional argument to the ctest_test() command
achieves a similar thing:
ctest_test(OUTPUT_JUNIT /path/to/resultFile.xml)ctest_test(OUTPUT_JUNIT /path/to/resultFile.xml)
JUnit 输出不支持命名测量和文件附件等功能。因此,它并不能完全替代 CDash 所能完成的功能,但减少的功能集对于许多项目来说可能仍然足够好。
Features like named measurements and file attachments are not supported by the JUnit output. It therefore isn’t a complete replacement for what can be accomplished with CDash, but the reduced feature set may still be good enough for many projects.
测试有时会产生大量输出。仪表板运行将截断超出可配置限制的报告输出,无论是在默认 CDash 结果还是在 JUnit XML 文件输出(启用的情况下)中。默认情况下,任何通过的测试的输出都将被截断为 1024 字节。对于失败的测试,输出将被截断为 307,200 字节(即 300kB)。可以通过在仪表板脚本中设置变量来覆盖这些限制。
CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE并可
CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE用于指定要截断的字节数,而不是默认值。这些变量长期以来一直受到 CMake 支持,但自 CMake 3.4 起才正式记录。
Tests can sometimes produce a lot of output.
A dashboard run will truncate the reported output beyond a configurable limit,
both in the default CDash results and in JUnit XML file output (where enabled).
By default, the output from any test that passes will be truncated at 1024
bytes.
For failing tests, the output will be truncated at 307,200 bytes (i.e. 300kB).
These limits can be overridden by setting variables in the dashboard script.
CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE and
CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE can be used to specify the
number of bytes to truncate at instead of the default values.
These variables have long been supported by CMake, but have only been
officially documented since CMake 3.4.
CTEST_FULL_OUTPUT使用 CMake 3.21 或更高版本,各个测试可以通过在输出中的某处记录特殊字符串来覆盖输出限制。然后,无论任何输出限制如何,该测试的整个输出都将包含在测试结果中。请谨慎使用此功能,因为它有可能显着增加报告和上传的数据量。
With CMake 3.21 or later, individual tests can override the output limits by
logging the special string CTEST_FULL_OUTPUT somewhere in their output.
The entire output from that test will then be included in the test results,
regardless of any output limit.
Use this feature sparingly, as it has the potential to significantly increase
the amount of data reported and uploaded.
CMake/ctest为构建、执行和确定测试的通过/失败状态提供支持。该项目负责提供测试代码本身,这就是像 GoogleTest 这样的测试框架可以发挥作用的地方。此类框架补充了 CMake 提供的功能,并ctest有助于编写清晰、结构良好的测试用例,这些测试用例可以很好地集成到 CMake 和ctest工作方式中。
CMake/ctest provide support for building, executing and determining pass/fail
status of tests. The project is responsible for providing the test code itself
and this is where testing frameworks like GoogleTest can be useful. Such
frameworks complement the features provided by CMake and ctest to facilitate
the writing of clear, well-structured test cases that integrate well into the
way CMake and ctest work.
FindGTestCMake长期以来一直通过模块支持 GoogleTest 。该模块搜索预构建的 GoogleTest 位置并创建项目可用于将 GoogleTest 合并到其构建中的变量。从 CMake 3.5 开始,还提供了导入目标,这比使用变量更受青睐。使用这些导入目标可以更可靠地处理使用要求和属性。如何在 CMake 3.5 或更高版本中使用该模块的简单示例类似于以下内容:
CMake has supported GoogleTest via a FindGTest module for quite a long time.
The module searches for a pre-built GoogleTest location and creates variables
that projects can use to incorporate GoogleTest into their build. From CMake
3.5, import targets are also provided, which are strongly preferred over the
use of variables. Using these import targets results in much more robust
handling of usage requirements and properties. A simple example of how to
use the module with CMake 3.5 or later would be similar to the following:
add_executable(MyGTestCases ...)
find_package(GTest REQUIRED)
target_link_libraries(MyGTestCases PRIVATE GTest::GTest)
add_test(NAME MyGTestCases COMMAND MyGTestCases)add_executable(MyGTestCases ...)
find_package(GTest REQUIRED)
target_link_libraries(MyGTestCases PRIVATE GTest::GTest)
add_test(NAME MyGTestCases COMMAND MyGTestCases)
导入目标负责确保在构建时使用相关的标头搜索路径MyGTestCases,并且在需要时链接适当的线程库等内容。上面的代码适用于所有平台,隐藏了与不同平台和编译器上使用的不同名称、运行时、标志等相关的相当多的复杂性。如果使用模块定义的变量而不是导入目标,这些事情大多必须手动处理,这是一项相当脆弱的任务。
The import target takes care of ensuring the relevant header search path is
used when building MyGTestCases and that things like the appropriate
threading library is linked in if needed. The above works on all platforms,
hiding a fair amount of complexity associated with different names, runtimes,
flags, etc. that are used on the different platforms and compilers. If using
the variables defined by the module instead of the import targets, these things
mostly have to be handled manually, which is a fairly fragile task.
更强大的方法是将 GoogleTest 的源代码直接合并到构建中,而不是依赖于可用的预构建二进制文件。这确保了 GoogleTest 使用与项目其余部分完全相同的编译器和链接器设置构建,从而避免了使用预构建的 GoogleTest 二进制文件时可能出现的许多微妙问题。项目可以通过多种方式来实现这一点,每种方式都有其优点和缺点。在项目中嵌入源代码和标头的副本是最简单的,但它会使项目与将来可能对 GoogleTest 进行的改进脱节。GoogleTest git 存储库可以作为 git 子模块添加到项目中,但这也有其自身的稳健性问题。作为配置步骤一部分下载 GoogleTest 源的第三个选项在第 28.2 节“FetchContent”中详细讨论,并且没有其他方法的缺点(CMake 3.11 中添加的功能也使该选项变得非常容易) 。
An even more robust approach is to incorporate GoogleTest’s sources directly into the build rather than relying on having pre-built binaries available. This ensures that GoogleTest is built with exactly the same compiler and linker settings as the rest of the project, which avoids many of the subtle issues that can arise when using pre-built GoogleTest binaries. Projects can do this in a number of ways, each with their advantages and drawbacks. Embedding a copy of the sources and headers in the project is the simplest, but it disconnects the project from improvements that may be made to GoogleTest in the future. The GoogleTest git repository can be added to the project as a git submodule, but that too comes with its own robustness issues. A third option of downloading the GoogleTest sources as part of the configure step is discussed in detail in Section 28.2, “FetchContent” and doesn’t have the drawbacks of the other methods (it is also made very easy with features added in CMake 3.11).
使用 GoogleTest 的测试可执行文件通常定义多个测试用例。运行可执行文件一次并假设它是单个测试用例的通常模式并不真正合适。理想情况下,每个 GoogleTest 测试用例都应该是可见的,ctest以便每个测试用例都可以单独运行和评估。该FindGTest模块提供了一个gtest_add_test()功能,可以扫描源代码,查找相关 GoogleTest 宏的使用,并提取每个单独的测试用例作为自己的ctest测试。该命令的传统形式如下:
A test executable that uses GoogleTest typically defines more than one test
case. The usual pattern of running the executable once and assuming it is a
single test case isn’t really appropriate. Ideally, each GoogleTest test case
should be visible to ctest so that each one can be run and assessed
individually. The FindGTest module provides a gtest_add_test() function
which scans the source code looking for uses of the relevant GoogleTest macros
and extracts out each individual test case as its own ctest test. The form of
this command has traditionally been the following:
gtest_add_tests(executable "extraArgs" sourceFiles..)gtest_add_tests(executable "extraArgs" sourceFiles..)
从 CMake 3.1 开始,要扫描的列表sourceFiles可以用关键字 替换AUTO。executable然后通过假设是 CMake 目标并使用其target 属性来获取源SOURCES。
From CMake 3.1, the list of sourceFiles to scan can be replaced by the
keyword AUTO.
The sources are then obtained by assuming executable is a CMake target and
using its SOURCES target property.
在 CMake 3.9 中,人们认识到项目可能希望使用由
gtest_add_tests()项目本身构建的 GoogleTest 的功能。这意味着该项目不需要 Find 模块,因此该功能被移出到新GoogleTest模块,FindGTest然后包含它以保持向后兼容性。作为该工作的一部分,还添加了带有关键字参数的函数的改进形式:
In CMake 3.9, it was recognized that projects may want to use the
gtest_add_tests() function with GoogleTest built by the project itself. This
meant the project didn’t need a Find module, so the function was moved out to a
new GoogleTest module and FindGTest then included it to maintain backward
compatibility. An improved form of the function with keyword arguments was also
added as part of that work:
gtest_add_tests(
TARGET target
[SOURCES src1...]
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[SKIP_DEPENDENCY]
[TEST_LIST outVar]
)gtest_add_tests(
TARGET target
[SOURCES src1...]
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[SKIP_DEPENDENCY]
[TEST_LIST outVar]
)
旧的表单仍然受支持,但项目应该尽可能使用新的表单,因为它更灵活、更健壮。例如,可以将相同的目标赋予具有gtest_add_tests()不同参数的多个调用,每个调用具有不同的TEST_PREFIX和/或
TEST_SUFFIX以区分生成的测试集。TEST_LIST新表格还提供了给出选项时找到的一组测试。有了可用的测试名称,项目就可以根据需要修改测试的属性。以下示例演示了这些不同的功能:
The old form is still supported, but projects should prefer to use the new form
instead where possible, since it is more flexible and more robust. For example,
the same target can be given to multiple calls to gtest_add_tests() with
different arguments, with each call having a different TEST_PREFIX and/or
TEST_SUFFIX to differentiate the sets of tests that get generated. The
new form also provides the set of tests found when the TEST_LIST option is
given. With the test names available, the project is able to modify the tests’
properties as needed. The following example demonstrates these various
capabilities:
# Assume GoogleTest is already part of the build, so we
# don't need FindGTest and can reference the gtest target
# directly
include(GoogleTest)
add_executable(TestDriver ...)
target_link_libraries(TestDriver PRIVATE gtest)
# Run the TestDriver twice with two different arguments
gtest_add_tests(
TARGET TestDriver
EXTRA_ARGS --algo=fast
TEST_SUFFIX .Fast
TEST_LIST fastTests
)
gtest_add_tests(
TARGET TestDriver
EXTRA_ARGS --algo=accurate
TEST_SUFFIX .Accurate
TEST_LIST accurateTests
)
set_tests_properties(${fastTests} PROPERTIES
TIMEOUT 3
)
set_tests_properties(${accurateTests} PROPERTIES
TIMEOUT 20
)
set(betaTests ${fastTests} ${accurateTests})
list(FILTER betaTests INCLUDE REGEX Beta)
set_tests_properties(${betaTests} PROPERTIES
LABELS Beta
)# Assume GoogleTest is already part of the build, so we
# don't need FindGTest and can reference the gtest target
# directly
include(GoogleTest)
add_executable(TestDriver ...)
target_link_libraries(TestDriver PRIVATE gtest)
# Run the TestDriver twice with two different arguments
gtest_add_tests(
TARGET TestDriver
EXTRA_ARGS --algo=fast
TEST_SUFFIX .Fast
TEST_LIST fastTests
)
gtest_add_tests(
TARGET TestDriver
EXTRA_ARGS --algo=accurate
TEST_SUFFIX .Accurate
TEST_LIST accurateTests
)
set_tests_properties(${fastTests} PROPERTIES
TIMEOUT 3
)
set_tests_properties(${accurateTests} PROPERTIES
TIMEOUT 20
)
set(betaTests ${fastTests} ${accurateTests})
list(FILTER betaTests INCLUDE REGEX Beta)
set_tests_properties(${betaTests} PROPERTIES
LABELS Beta
)
上面的示例创建了两组测试并对它们应用不同的超时限制。每组中的测试名称将具有不同的后缀。如果没有这些TEST_SUFFIX选项,第二次调用gtest_add_tests()将会失败,因为它将尝试创建与第一次调用同名的测试。该示例还Beta为某些测试设置了标签,无论它们属于哪个测试集。
The above example creates two sets of tests and applies different timeout
limits to them.
The test names will have different suffixes in each group.
Without the TEST_SUFFIX options, the second call to gtest_add_tests() would
fail because it would try to create tests with the same name as the first call.
The example also sets a Beta label to some tests regardless of which test set
they belong to.
虽然gtest_add_tests()对于简单情况和没有异常格式的源文件效果很好,但它不能处理参数化测试或通过自定义宏定义的测试。每当测试源发生变化时,还需要重新运行 CMake 来重新扫描源文件。如果 CMake 步骤不快,则在处理测试代码时可能会令人沮丧,因为每次更改后 CMake 将被迫重新运行以进行下一个构建。该SKIP_DEPENDENCY
选项可以防止这种行为,并依赖开发人员手动重新运行 CMake 来更新测试集,但这更多的是在积极进行测试时的临时解决方法,而不是应永久保留的方法。
While gtest_add_tests() works well for simple cases and source files that
don’t have unusual formatting, it doesn’t cope with parameterized tests or
tests defined through custom macros. It also requires re-running CMake to
re-scan the source files whenever the test sources change. If the CMake step
isn’t fast, it can be frustrating when working on the test code as CMake will
be forced to re-run for the next build after each change. The SKIP_DEPENDENCY
option prevents that behavior and relies on the developer manually re-running
CMake to update the set of tests, but this is more a temporary workaround when
actively working on a test than something that should be left permanently in
place.
gtest_add_tests()在 CMake 3.10 中,添加了一个新函数来解决要求可执行文件列出其测试
的缺点
。此查询是在构建期间或运行时执行的,ctest而不是在配置阶段扫描源代码。因此,无论何时更改测试源,都不需要重新运行 CMake,可以稳健地处理参数化测试,并且对测试的格式或定义方式没有限制。代价是测试列表在 CMake 运行期间不可用。
In CMake 3.10, a new function was added to address the shortcomings of
gtest_add_tests() by asking the executable to list its tests.
This query is performed during the build or when running ctest rather than
scanning the source code during the configure stage.
Because of this, CMake does not need to be re-run whenever the test source is
changed, parameterized tests are handled robustly and there is no restriction
on the formatting or way that the tests are defined.
The trade-off is that the list of tests is not available during the CMake run.
gtest_discover_tests(target
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[NO_PRETTY_TYPES]
[NO_PRETTY_VALUES]
[PROPERTIES name1 value1...]
[TEST_LIST var]
[DISCOVERY_TIMEOUT seconds] # See notes below
[DISCOVERY_MODE] # CMake 3.18 or later
)gtest_discover_tests(target
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[NO_PRETTY_TYPES]
[NO_PRETTY_VALUES]
[PROPERTIES name1 value1...]
[TEST_LIST var]
[DISCOVERY_TIMEOUT seconds] # See notes below
[DISCOVERY_MODE] # CMake 3.18 or later
)
默认情况下,当生成参数化测试的名称时,该函数将尝试使用类型或值名称而不是数字索引。这通常会产生更易读和更有用的名称,但对于那些不希望出现这种情况的情况,可以使用NO_PRETTY_TYPES和NO_PRETTY_VALUES选项来抑制替换并仅使用索引值。
By default, when generating the names of parameterized tests, the function will
attempt to use type or value names rather than a numerical index. This will
generally result in much more readable and useful names, but for those cases
where this is undesirable, the NO_PRETTY_TYPES and NO_PRETTY_VALUES options
can be used to suppress the substitution and just use the index values.
该DISCOVERY_TIMEOUT选项是指运行可执行文件以获得测试列表所需的时间。默认值 5 秒对于所有可执行文件来说应该足够了,但那些具有大量测试或某些其他行为导致需要很长时间才能返回测试列表的可执行文件除外。此选项最初是在 CMake 3.10.1 中使用关键字 name 添加的
TIMEOUT,但发现它会导致名称与TIMEOUT测试属性发生冲突,从而导致意外但合法的行为。在 CMake 3.10.3 中该关键字已更改为 ,DISCOVERY_TIMEOUT以防止出现这些情况。
The DISCOVERY_TIMEOUT option refers to the time taken to run the executable
to obtain the list of tests.
The default of 5 seconds should be sufficient for all but those executables
with a huge number of tests or some other behavior that causes it to take a
long time to return the test list.
This option was originally added in CMake 3.10.1 with the keyword name
TIMEOUT, but it was found to cause name clashes with the TIMEOUT test
property in a way that led to unexpected but legal behavior.
The keyword was changed to DISCOVERY_TIMEOUT in CMake 3.10.3 to prevent those
scenarios.
由于测试列表不会返回给调用者,因此无法调用set_tests_properties()或set_property()修改已发现测试的属性。相反,gtest_discover_tests()允许将属性及其值指定为调用的一部分,然后将其写入
输入文件以在运行ctest时应用。ctest虽然不提供能够迭代 CMake 中已发现的测试集并单独处理它们的所有灵活性,但将已发现的测试的属性设置为一个整体的能力通常是所需要的,并且通常不是一个重大限制。主要的例外是,无法设置名称与命令中的关键字相对应的测试属性gtest_discover_tests(),或者属性需要列表值的情况。必须使用自定义ctest脚本来处理此类情况,下面给出了一个示例。
Since the list of tests is not returned to the caller, it is not possible to
call set_tests_properties() or set_property() to modify properties of the
discovered tests. Instead, gtest_discover_tests() allows properties and their
values to be specified as part of the call, which are then written into the
ctest input file to be applied when ctest is run. While not providing all
the flexibility of being able to iterate through the set of discovered tests in
CMake and processing them individually, the ability to set properties of the
discovered tests as a whole is usually all that is needed and is not typically
a significant restriction. The main exception to this is that it is not
possible to set test properties that have names which correspond to keywords in
the gtest_discover_tests() command, or where properties require values that
are lists. A custom ctest script must be used to handle such cases, an
example of which is given below.
该选项对于 的工作方式与对于 的
TEST_LIST工作方式不同。在这种情况下,通过此选项给出的变量名称将在CMake 写出的输入文件中使用,而不是直接供 CMake 使用。仅当项目将一些自己的自定义逻辑添加到生成的输入文件并想要引用生成的测试列表时,才需要该选项。即使如此,只有在多次调用中使用同一目标时才需要这样做。如果选项未设置,则使用
默认变量名称。gtest_discover_tests()gtest_add_tests()ctestTEST_LISTctestgtest_discover_tests()<target>_TESTSTEST_LIST
The TEST_LIST option works differently for gtest_discover_tests() than for
gtest_add_tests(). In this case, the variable name given with this option is
used in the ctest input file written out by CMake rather than being available
to CMake directly. The TEST_LIST option would only be needed if the project
adds some of its own custom logic to the generated ctest input file and wants
to refer to the list of generated tests. Even then, only if the same target is
being used in multiple calls to gtest_discover_tests() would this be
necessary. A default variable name of <target>_TESTS is used if not set by a
TEST_LIST option.
可以通过将文件名附加到目录属性中保存的文件列表来添加自定义代码TEST_INCLUDE_FILES。项目不得覆盖此目录属性,它们只能附加到它,因为
gtest_discover_tests()使用该属性来构建要由 读取的文件集ctest。
Custom code can be added by appending file names to the list of files held in
the TEST_INCLUDE_FILES directory property. Projects must not overwrite
this directory property, they should only append to it since
gtest_discover_tests() uses the property to build up the set of files to be
read by ctest.
以下示例演示如何使用自定义文件来操作已发现测试的属性,并实现与前面的示例相同的等效逻辑,包括
名称冲突极端情况gtest_add_tests()的解决方法:TIMEOUT
The following example shows how to use a custom file to manipulate properties
on discovered tests and implement the same equivalent logic as the earlier
example for gtest_add_tests(), including a workaround for the TIMEOUT
name clash corner case:
gtest_discover_tests(
TestDriver
EXTRA_ARGS --algo=fast
TEST_SUFFIX .Fast
TEST_LIST fastTests
)
gtest_discover_tests(
TestDriver
EXTRA_ARGS --algo=accurate
TEST_SUFFIX .Accurate
TEST_LIST accurateTests
)
set_property(DIRECTORY APPEND PROPERTY
TEST_INCLUDE_FILES
${CMAKE_CURRENT_LIST_DIR}/customTestManip.cmake
)gtest_discover_tests(
TestDriver
EXTRA_ARGS --algo=fast
TEST_SUFFIX .Fast
TEST_LIST fastTests
)
gtest_discover_tests(
TestDriver
EXTRA_ARGS --algo=accurate
TEST_SUFFIX .Accurate
TEST_LIST accurateTests
)
set_property(DIRECTORY APPEND PROPERTY
TEST_INCLUDE_FILES
${CMAKE_CURRENT_LIST_DIR}/customTestManip.cmake
)
# Set here to work around the TIMEOUT keyword clash for the
# gtest_discover_tests() call, works for all CMake versions
set_tests_properties(${fastTests} PROPERTIES
TIMEOUT 3
)
set_tests_properties(${accurateTests} PROPERTIES
TIMEOUT 20
)
set(betaTests ${fastTests} ${accurateTests})
list(FILTER betaTests INCLUDE REGEX Beta)
set_tests_properties(${betaTests} PROPERTIES
LABELS Beta
)# Set here to work around the TIMEOUT keyword clash for the
# gtest_discover_tests() call, works for all CMake versions
set_tests_properties(${fastTests} PROPERTIES
TIMEOUT 3
)
set_tests_properties(${accurateTests} PROPERTIES
TIMEOUT 20
)
set(betaTests ${fastTests} ${accurateTests})
list(FILTER betaTests INCLUDE REGEX Beta)
set_tests_properties(${betaTests} PROPERTIES
LABELS Beta
)
使用自定义ctest脚本会增加项目的复杂性,但它允许完全控制测试属性。不用担心名称冲突,gtest_discover_tests()并且可以安全地处理具有列表值的属性。
Using a custom ctest script adds a little more complexity to the project, but
it allows full control over test properties. There is no concern about name
clashes with gtest_discover_tests() and properties with list values can be
handled safely.
POST_BUILD对于 CMake 3.17 及更早版本,在构建过程中的一个步骤是查询可执行文件以获取测试列表。使用 CMake 3.18 或更高版本,可以将查询测试列表推迟到ctest运行。这样做至少有以下几个优点:
With CMake 3.17 and earlier, the executable is queried for the list of tests as
a POST_BUILD step during the build.
With CMake 3.18 or later, it is possible to defer querying the list of tests
until ctest is run.
This has at least the following advantages:
PATH设置环境变量,以便可以找到所有测试可执行文件的 DLL。这不仅不方便,而且还有可能改变事物的构建方式。将查询推迟到测试阶段意味着可执行文件不必在构建时运行,从而避免了这些问题。
PATH environment variable to be set such that all of the test
executable’s DLLs can be found. This is not only inconvenient, it also has
the potential to change the way things are built. Deferring the query to the
test stage means executables don’t have to be runnable at build time, thereby
avoiding these problems.
该DISCOVERY_MODE选项控制何时执行测试列表查询。它从变量中获取默认值
CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE。如果未设置该变量,则该模式默认为POST_BUILD保留向后兼容性。POST_BUILD可以将模式设置为 ,而不是使用PRE_TEST,这会将查询延迟到测试阶段。该PRE_TEST模式应该始终是更可取的,并且通过变量在项目范围内设置它通常会更方便,而不是在每次调用时显式设置它gtest_discover_tests()。
The DISCOVERY_MODE option controls when the test list query is performed.
It gets its default value from the
CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE variable.
If that variable is not set, the mode defaults to POST_BUILD to preserve
backward compatibility.
Instead of using POST_BUILD, the mode can be set to PRE_TEST, which delays
the query until the test stage.
The PRE_TEST mode should always be preferable and it will generally be
more convenient to set it project-wide via the variable instead of explicitly
setting it in every call to gtest_discover_tests().
旨在使每个测试的名称简短,但又足够具体到测试的性质,以便使用正则表达式以及给定的 和选项-R轻松缩小测试集范围。避免包含
在名称中,因为它会向测试输出添加额外的内容,但没有任何好处。-Ectesttest
Aim to make the name of each test short, yet sufficiently specific to the
nature of the test so it is easy to narrow down a test set using regular
expressions with the -R and -E options given to ctest. Avoid including
test in the name, since it adds extra content to the test output with no
benefit.
假设有一天该项目可能会被合并到一个更大的项目层次结构中,该层次结构可能有许多其他测试。旨在使用足够具体的测试名称,以减少名称冲突的可能性。更重要的是,更愿意让父项目控制是否添加测试,并定义默认行为仅在没有父项目时添加测试。使用非缓存变量来实现控件,以便父项目可以选择是否将其公开在缓存中。合适的变量名称是TEST_XXX大写XXX的项目名称。以下示例演示了名为 的顶级项目的这种安排FooBar:
Assume that the project may one day be incorporated into a much larger
hierarchy of projects which may have many other tests. Aim to use test names
that are specific enough to reduce the chances of name clashes. More
importantly, prefer to give parent projects control over whether to add the
tests at all and define the default behavior to only add tests if there is no
parent project. Use a non-cache variable to implement the control so that a
parent project can choose whether to expose it in the cache or not. A suitable
variable name would be TEST_XXX where XXX is the uppercased project name.
The following example demonstrates such an arrangement for a top level project
called FooBar:
if(TEST_FOOBAR OR
CMAKE_SOURCE_DIR STREQUAL FooBar_SOURCE_DIR)
add_test(...)
endif()if(TEST_FOOBAR OR
CMAKE_SOURCE_DIR STREQUAL FooBar_SOURCE_DIR)
add_test(...)
endif()
为了进一步改进与父项目的集成,请考虑使用
LABELS测试属性为每个测试包含特定于项目的标签。ctest这些每个项目的标签应该允许通过给via-L和选项的正则表达式轻松包含或排除测试-LE。测试可以有多个标签,因此这对如何使用标签没有限制,但可能很难确保在项目的所有测试上严格设置特定于项目的标签。
To further improve integration with parent projects, consider using the
LABELS test property to include a project-specific label for each test.
These per-project labels should allow tests to be easily included or excluded
by regular expressions given to ctest via -L and -LE options. Tests can
have multiple labels, so this places no restriction on how else labels can be
used, but it may be difficult to ensure that the project-specific label is
rigorously set on all of a project’s tests.
标签的另一个很好的用途是识别预计需要很长时间才能运行的测试。开发人员和持续集成系统可能希望降低运行频率,因此能够根据测试标签排除它们可能非常方便。考虑向运行时间较长且不需要经常运行的测试添加标签。在没有任何其他现有约定的情况下,标签LongRunning是一个不错的选择。
Another good use of labels is to identify tests that are expected to take a
long time to run. Developers and continuous integration systems may want to run
these less frequently, so being able to exclude them based on test labels can
be very convenient. Consider adding a label to tests that run for a non-trivial
amount of time and that don’t need to run as often. In the absence of any other
existing convention, a label of LongRunning is a good choice.
除了使用正则表达式匹配测试名称和标签之外,还可以将测试集缩小到特定目录及以下目录。ctest它可以从构建树下面的子目录运行,而不是从构建树的顶部运行。只有那些从该目录的关联源目录及以下目录定义的测试才会被ctest. 为了能够充分利用这一点,测试不应全部收集在一个地方并在没有目录结构的情况下进行定义。让测试与正在测试的源代码保持接近可能很有用,这样源代码的自然目录结构就可以被重新使用,从而为测试提供结构。如果源代码曾经被移动过,这种方法也使得移动相关测试变得更加容易。
As well as using regular expression matching against test names and labels, it
is also possible to narrow the set of tests down to a particular directory and
below. Instead of running ctest from the top of the build tree, it can be run
from subdirectories below it. Only those tests defined from that directory’s
associated source directory and below will be known to ctest. To be able to
take full advantage of this, tests should not all be collected together in one
place and defined with no directory structure. It may be useful to keep tests
close to the source code they are testing so that the natural directory
structure of the source code can be re-used to also give structure to the
tests. If the source code is ever moved around, this approach also makes it
easier to move the associated tests with it.
编写简单地打开大量日志记录然后使用通过/失败正则表达式来确定成功的测试可能很诱人。这可能是一种相当脆弱的方法,因为开发人员经常更改记录的输出,假设它只是为了提供信息。将时间戳添加到记录的输出中使该方法进一步复杂化。与依赖匹配的记录输出相比,在可能的情况下更愿意让测试代码本身通过显式测试预期的前置条件和后置条件、中间值等来确定成功或失败状态。像 GoogleTest 这样的测试框架使得编写和维护这样的代码变得容易。测试相当容易,并且强烈推荐(哪个框架不如至少使用 一些合适的框架重要)。
It can be tempting to write tests that simply turn on a lot of logging and then use pass/fail regular expressions to determine success. This can be a fairly fragile approach, as developers frequently change logged output under the assumption that it is just for informational purposes. Adding timestamps into the logged output further complicates that approach. Rather than relying on matching logged output, where possible prefer to make the test code itself determine the success or failure status by explicitly testing the expected pre- and post-conditions, intermediate values, etc. Testing frameworks such as GoogleTest make writing and maintaining such tests considerably easier and are strongly recommended (which framework is less important than at least using some suitable framework).
如果使用 GoogleTest 框架,请考虑使用该模块提供的gtest_add_tests()和
函数。如果测试代码足够简单,足以找到所有测试,则它提供了操作各个测试属性的最简单、最灵活的方法,但在处理测试代码本身时可能不太方便,因为它可能需要经常重新运行 CMake。如果项目可以要求 CMake 3.10.3 或更高版本作为最低版本,那么可能更合适。此函数的主要缺点是,将测试属性设置为列表值需要更多工作,如果遵循上述有关使用测试标签的建议,这一点尤其重要。如果需要支持 3.9 之前的 CMake 版本,则只能使用更简单的命令形式。该项目还需要使用该
模块而不是该模块,如果将 GoogleTest 作为项目本身的一部分构建,这会进一步增加复杂性。因此,如果使用 GoogleTest,强烈建议项目迁移到 CMake 3.9 或更高版本,最好迁移到 3.10.3 或更高版本。该变量还应设置为
提供更强大和更方便的行为
(优点适用于 CMake 3.18 或更高版本)。gtest_discover_tests()GoogleTestgtest_add_tests()gtest_discover_tests()gtest_add_tests()FindGTestGoogleTestCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODEPRE_TESTgtest_discover_tests()
If using the GoogleTest framework, consider using the gtest_add_tests() and
gtest_discover_tests() functions provided by the GoogleTest module. If the
test code is simple enough for gtest_add_tests() to find all tests, it offers
the simplest and most flexible way of manipulating individual test properties,
but it can be less convenient while working on the test code itself since it
can require re-running CMake frequently. If the project can require CMake
3.10.3 or later as a minimum version, then gtest_discover_tests() may be more
suitable. The main drawback to this function is that setting test properties
to values that are lists requires more work, which is particularly relevant if
following the advice above regarding the use of test labels. If supporting
CMake versions before 3.9 is required, only gtest_add_tests() can be used and
only the simpler form of the command. The project will also need to use the
FindGTest module rather than the GoogleTest module, which adds further
complexity if GoogleTest is being built as part of the project itself.
Projects are therefore strongly advised to move to CMake 3.9 or later if using
GoogleTest and ideally 3.10.3 or later.
The CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE variable should also be set to
PRE_TEST to give more robust and more convenient behavior for
gtest_discover_tests() (advantages apply with CMake 3.18 or later).
对于可以针对不同目标平台进行交叉编译的项目,请考虑是否可以编写测试以在模拟器下运行,或者通过脚本或等效机制在远程系统上复制和执行。CMake 的CMAKE_CROSSCOMPILING_EMULATOR变量和关联的
CROSSCOMPILING_EMULATOR目标属性可用于实现这两种策略之一。理想情况下,CMAKE_CROSSCOMPILING_EMULATOR将在用于交叉编译的工具链文件中设置。
For projects where cross-compiling for a different target platform is a
possibility, consider whether tests can be written to run under an emulator or
be copied and executed on a remote system via a script or an equivalent
mechanism.
CMake’s CMAKE_CROSSCOMPILING_EMULATOR variable and the associated
CROSSCOMPILING_EMULATOR target property can be used to implement either of
these strategies.
Ideally, CMAKE_CROSSCOMPILING_EMULATOR would be set in the toolchain file
used for the cross-compilation.
充分利用ctest. 如果已知测试使用多个 CPU,请设置这些测试的PROCESSORS
属性,以便ctest为如何安排它们提供更好的指导。如果测试需要对共享资源进行独占访问,请使用该RESOURCE_LOCK
属性来控制对该资源的访问,并避免使用RUN_SERIAL
测试属性,除非没有其他选择。RUN_SERIAL可能会对并行测试性能产生很大的负面影响,并且除了快速、临时的开发人员实验之外,很少有合理的理由。如果正在运行的计算机ctest可能有其他进程造成 CPU 负载,请考虑使用该-l选项来帮助限制 CPU 过度使用。这在开发人员机器上特别有用,开发人员可能同时为多个项目构建和运行测试。
Make the most of the support for parallel test execution in ctest. Where
tests are known to use more than one CPU, set those tests’ PROCESSORS
property to provide better guidance to ctest for how to schedule them. If
tests need exclusive access to a shared resource, use the RESOURCE_LOCK
property to control access to that resource and avoid using the RUN_SERIAL
test property unless there is no other alternative. RUN_SERIAL can have a big
negative impact on parallel test performance and is rarely justified apart from
quick, temporary developer experiments. If the machine on which ctest is
being run may have other processes contributing to the CPU load, consider using
the -l option to help limit the CPU over-commit. This can be especially
useful on developer machines where developers may be building and running tests
for multiple projects simultaneously.
如果最低 CMake 版本可以设置为 3.7 或更高版本,则更喜欢使用测试装置来定义测试之间的依赖关系。定义测试用例来设置和清理其他测试所需的资源,启动和停止服务等。当由于正则表达式匹配或类似选项而使用减少的测试集运行时--rerun-failed,ctest会自动将所需的夹具测试添加到测试集。夹具还确保跳过依赖项失败的测试,这与DEPENDS仅控制测试顺序而不强制执行成功要求的测试属性不同。要精细控制哪些测试将自动添加到测试集中以满足夹具依赖性,请使用 CMake 3.9 或更高版本作为 options ctest
,-FS并-FC在-FA该版本中添加。项目仍然只需要 CMake 3.7 作为最低版本。TIMEOUT_AFTER_MATCH由于更清晰的依赖关系和时序控制,也更喜欢使用固定装置而不是测试属性。
If the minimum CMake version can be set to 3.7 or later, prefer to use test
fixtures to define dependencies between tests. Define test cases to setup and
clean up resources required by other tests, to start and stop services and so
on. When running with a reduced test set as a result of regular expression
matching or options like --rerun-failed, ctest automatically adds the
required fixture tests to the test set. Fixtures also ensure that tests whose
dependencies fail are skipped, unlike the DEPENDS test property which merely
controls test order without enforcing a success requirement. To gain
fine-grained control over which tests will be automatically added to the test
set to satisfy fixture dependencies, use CMake 3.9 or later for the ctest
options -FS, -FC and -FA added in that release. Projects can still
require only CMake 3.7 as a minimum version. Also prefer to use fixtures over
the TIMEOUT_AFTER_MATCH test property due to the clearer dependency
relationship and timing control.
构建ctest和测试模式是将小型测试构建合并为主项目测试套件中的测试用例的有用方法。当其中一些测试构建需要验证某些情况是否会导致配置或构建错误时,这些方法尤其有效。由于测试用例可以定义为预期失败,因此它们可以验证此类条件,而不会导致主项目的构建失败。考虑使用ctest构建和测试模式作为定义此类测试用例的COMMAND调用的一部分。add_test()
The ctest build and test mode can be a useful way of incorporating small test
builds off to the side as test cases in the main project’s test suite. These
can be especially effective when some of those test builds need to verify that
certain situations lead to configure or build errors. Since test cases can be
defined as expected to fail, they can verify such conditions without making the
main project’s build fail. Consider using the ctest build and test mode as
the COMMAND part of a call to add_test() to define such test cases.
为了运行主项目的完整配置、构建和测试管道,请考虑 CDash 集成功能提供的功能,而不是使用ctest构建和测试模式。它们可以更好地捕获整个管道的输出并提供定制每个步骤行为的机制。它还具有有助于使用代码覆盖率和动态分析工具(例如内存检查器、消毒器等)的附加功能,无论是否将结果提交到 CDash 服务器,都可以使用这些功能。事实上,ctest驱动整个 CDash 管道的自定义脚本功能可以在没有 CDash 的情况下使用,这使其成为一种潜在的方便的独立于平台的方式,为其他持续集成系统编写整个构建和测试管道的脚本。CMake 3.21 或更高版本提供的 JUnit 输出支持在这种情况下特别有用。CDash 服务器还可以与其他 CI 系统结合使用,以提供更丰富的功能集,用于记录和比较构建历史、测试失败趋势等。
For running the complete configure, build and test pipeline of the main
project, consider the functionality offered by the CDash integration features
rather than using the ctest build and test mode. These do a better job of
capturing output from the whole pipeline and providing mechanisms for
customizing each step’s behavior. It also has additional features that
facilitate using code coverage and dynamic analysis tools such as memory
checkers, sanitizers, etc. and these features can be used whether submitting
results to a CDash server or not. In fact, the custom ctest scripting
functionality that drives the whole CDash pipeline can be used without CDash,
making it a potentially convenient platform independent way of scripting the
whole build and test pipeline for other continuous integration systems as well.
The JUnit output support available with CMake 3.21 or later can be especially
useful in that scenario.
A CDash server can also be used in conjunction with other CI systems to provide
a richer set of features for recording and comparing build histories, test
failure trends and so on.
对于 CMake 3.18 或更高版本,该ctest --stop-on-failure选项可用于在遇到第一个错误时立即结束测试运行。这可以是开发过程中节省时间的措施,因为任何失败都可能与开发人员当时正在处理的区域有关。它还可用于快速结束持续集成运行,以便尽早报告故障。这样做的代价是仅提供有关可能的许多错误之一的反馈,因此通常仅应在运行所有测试的时间很长时才考虑。该命令STOP_ON_FAILURE的选项ctest_test()具有类似的效果,但将此行为硬编码到仪表板脚本中可能不如使用--stop-on-failure命令行选项灵活。
With CMake 3.18 or later, the ctest --stop-on-failure option can be used to
end a test run immediately upon the first error encountered.
This can be a time-saving measure during development where any failure is
likely to be related to the area the developer is working on at the time.
It can also be used to quickly end a continuous integration run so that the
failure can be reported as early as possible.
This comes at the expense of only providing feedback about one of possibly many
errors, so it should normally only be considered when the time to run all tests
is high.
The STOP_ON_FAILURE option to the ctest_test() command has a similar
effect, but hard-coding this behavior into a dashboard script may be less
flexible than using the --stop-on-failure command line option.
在开发项目源代码、创建各种资源、使构建健壮并实施自动化测试的所有艰苦工作之后,使软件可供分发的最后一步至关重要。它直接影响最终用户对项目的第一印象,如果做得不好,可能会导致软件在有机会使用之前就被拒绝。
After all the hard work of developing the source code of a project, creating its various resources, making the build robust and implementing automated tests, the final step of making the software available for distribution is critical. It has a direct effect on the end user’s first impressions of the project and if done poorly, may result in the software being rejected before it even gets a chance to be used.
开发人员和用户对于如何提供项目可能有不同的期望。对于某些人来说,只需提供对源代码存储库的访问并期望最终用户自行检查和构建就足够了。虽然这可能是交付模型的一部分,但并非所有最终用户都希望参与如此低的级别。相反,他们经常期望有一个预构建的二进制包,可以在他们的机器上安装和使用,最好是通过一些已经熟悉的包管理系统。考虑到所涉及的包管理器和交付格式的多样性,这可能给项目维护人员带来艰巨的挑战。然而,它们中的大多数之间有足够的共同元素,通过一些明智的规划,可以支持大多数流行的元素并覆盖所有主要平台。
Developers and users may have different expectations for how a project should be made available. For some, simply providing access to the source code repository and expecting end users to checkout and build it themselves is adequate. While this may be part of the delivery model, not all end users may want to get involved at such a low level. Instead, they will frequently expect a pre-built binary package that they can install and use on their machine, preferably via some already familiar package management system. Given the variety of package managers and delivery formats involved, this can present a daunting challenge for project maintainers. Nevertheless, there are enough common elements between most of them that with some judicious planning, it is possible to support most of the popular ones and cover all major platforms.
在项目生命周期中越早考虑交付阶段,最终的打包和部署阶段就可能越顺利。一个好的起点是在开发开始之前提出以下问题,或者对于现有项目尽早提出以下问题:
The earlier in a project’s life cycle the delivery phase is considered, the smoother the final packaging and deployment phases are likely to be. A good starting point is to ask the following questions before development begins, or as early as possible for existing projects:
PATH项目是否期望通过用户的环境变量在部署计算机上提供一个或多个可执行文件?项目的某些部分是否不应在 上公开PATH?
PATH environment variable? Are
there parts of the project which should not be exposed on the PATH?
这些问题将强烈影响软件在安装时的布局方式,进而影响源代码需要如何访问其自身的资源等等。它甚至可能会影响软件可用的功能,因此尽早了解这些事情可以节省以后浪费的精力。
These questions will strongly impact how the software is laid out when installed, which in turn affects how the source code needs to access its own resources and so on. It may even impact the functionality available to the software, so understanding these things early can save wasted effort later.
本章重点介绍布局方面以及如何将必要的文件组装到所需的位置。它还演示了如何通过提供配置包支持来使项目易于其他 CMake 项目使用。来自某些背景的开发人员可能会认为这些方面属于make install. 下一章通过讨论 CMake 和 CPack 可以生成的各种包格式来完成整个图景。该支持的实现使用此处描述的安装功能来安装到干净的暂存区域,然后从这些内容生成最终包。
This chapter focuses on the layout aspects and how to assemble the necessary
files in their required locations. It also demonstrates how to make a project
easy for other CMake projects to consume by providing config package support.
Developers from some backgrounds may identify with these aspects as belonging
to the realm of make install. The next chapter completes the picture by
discussing the various package formats that CMake and CPack can produce. The
implementation of that support uses the install functionality described here to
install to a clean staging area and then produce the final packages from those
contents.
在决定如何部署已安装的产品之前,了解部署平台施加的限制是一个重要的步骤。只有明确这些细节后,CMake 项目才能开始定义将什么安装到哪里。可以进行一些高层观察,这些观察可能会对项目的安装布局产生重大影响。
Understanding the constraints imposed by the deployment platform(s) is an essential step before decisions can be made about how an installed product should be laid out. Only once those details are clear can a CMake project go about defining what to install to where. A few high level observations can be made which potentially have a strong influence on the installed layout of a project.
PATH,以便可以从终端或命令行轻松调用它们。在 Windows 上,如果项目安装PATH通过添加还包含一些自己的 DLL 的目录来修改,其他应用程序可能会拾取这些 DLL,而不是预期的 DLL(例如,从它们自己的私有目录或标准系统之一) -广泛的地点)。来自流行工具包(例如 Qt)的 DLL 经常成为这种情况的受害者,因为包以PATH不应该的方式修改了它们。如果一个项目想要增强PATH它自己的可执行文件,它应该确保该目录中不存在 DLL,但这与将 DLL 与可执行文件放在同一目录中以便 Windows 可以在以下位置找到它们的需要直接矛盾。运行。典型的解决方案是创建一个仅包含启动脚本的目录,然后可以将其安全地添加到PATH.
PATH so they can be invoked easily from a terminal or command line. On
Windows, if a project installation modifies the PATH by adding a directory
that also contains some of its own DLLs, other applications may then pick up
those DLLs instead of the ones that were expected (e.g. from their own private
directories or one of the standard system-wide locations). DLLs from popular
toolkits such as Qt regularly fall victim to this scenario as a result of
packages modifying the PATH in ways they shouldn’t. If a project wants to
augment the PATH for its own executables, it should ensure that no DLLs are
present in that directory, but this is directly at odds with the need to have
the DLLs in the same directory as executables so that Windows can find them at
run time. The typical solution to this is to create a directory containing only
launch scripts which can then safely be added to the PATH.
除了在 Apple 平台上的部署之外,所有主要平台都存在很大程度的共性(或至少是潜在的共性)。安装位置可以被认为由基本路径和该路径下方的相对布局组成。基本路径可能类似于
/usr/…、/opt/…或 ,C:\Program Files并且显然在平台之间差异很大,但该基点下方的相对布局通常非常相似。常见的安排是将可执行文件(对于 Windows,还有 DLL)安装到bin目录、库或其lib某些变体以及include目录下的标头。其他文件类型在通常安装的位置上有更多的可变性,但这三种文件类型已经涵盖了项目将安装的一些最重要的文件类型。
With the exception of deployments to Apple platforms, there is a large degree
of commonality (or at least potential commonality) across all the major
platforms. The install location can be thought of as consisting of a base path
and a relative layout below that path. The base path may be something like
/usr/…, /opt/… or C:\Program Files and obviously varies widely
between platforms, but the relative layout below that base point is often very
similar. A common arrangement sees executables (and for Windows, also DLLs)
installed to a bin directory, libraries to lib or some variant thereof and
headers under an include directory. Other file types have somewhat more
variability in where they are typically installed, but these three already
cover some of the most important file types a project will install.
在 Windows 上,包的另一种变体是将可执行文件和 DLL 放在基本安装位置而不是子目录下bin。虽然这可能是一种相对常见的做法,但它可能会导致基本目录相当拥挤,使用户更难找到其他包组件。另一种变体是启动脚本位于名为 的子目录中cmd,这使它们与其他位置(例如bin.
On Windows, another variation is for packages to put executables and DLLs at
the base install location rather than under a bin subdirectory. While this
may be a relatively common practice, it can lead to a fairly crowded base
directory, making it harder for users to find other package components. Another
variation is for launch scripts to be located in a subdirectory named cmd,
which keeps them separated from DLLs in other locations such as bin.
找到适用于大多数平台的目录结构是可取的,因为它最大限度地减少了项目源代码必须实现的特定于平台的逻辑。如果项目在所有平台上使用相同的相对布局,则应用程序可以更轻松地在运行时找到所需的内容。
Finding a directory structure that works for most platforms is desirable, since it minimizes the platform-specific logic that has to be implemented by the project’s source code. If the project uses the same relative layout on all platforms, it is easier for an application to find things it needs at run time.
在没有任何其他要求的情况下,CMake 的GNUInstallDirs模块提供了一种非常方便的方法来使用标准目录布局。它与上面提到的常见情况一致,并且还提供了同时符合 GNU 编码标准和 FHS 的各种其他标准位置。抛开与基本安装路径相关的部分(在下一节中介绍),该布局甚至可以用于 Windows 部署。从 CMake 3.14 开始,许多与安装相关的命令从GNUInstallDirs非常相似的位置获取默认值或后备位置。
In the absence of any other requirements, CMake’s GNUInstallDirs module
provides a very convenient way to use a standard directory layout. It is
consistent with the common cases mentioned above and it also provides various
other standard locations that conform to both GNU coding standards and the FHS.
Putting aside the parts that relate to the base install path (covered in the
next section), the layout can even be used for Windows deployments.
Starting with CMake 3.14, a number of install-related commands take their
defaults from GNUInstallDirs or fall-back locations that are very similar.
使用该GNUInstallDirs模块相当简单,它像任何其他模块一样包含在内:
Using the GNUInstallDirs module is fairly straightforward, it is included
like any other module:
# Minimal inclusion, but see caveat further below
include(GNUInstallDirs)# Minimal inclusion, but see caveat further below
include(GNUInstallDirs)
这将创建以下形式的缓存变量CMAKE_INSTALL_<dir>,其中
<dir>表示特定位置。该模块的文档提供了所有已定义位置的完整详细信息,但一些更常用的位置及其预期用途包括:
This will create cache variables of the form CMAKE_INSTALL_<dir> where
<dir> denotes a particular location. The module’s documentation gives full
details of all the defined locations, but some of the more commonly used ones
and their intended use include:
BINDIR
BINDIR
bin.
bin.
SBINDIR
SBINDIR
BINDIR除了供系统管理员使用之外。默认为sbin.
BINDIR except intended for system admin use. Defaults
to sbin.
LIBDIR
LIBDIR
lib或某些变化取决于主机/目标平台(可能包括进一步的特定于体系结构的子目录)。
lib or some variation of
that depending on the host/target platform (including possibly a further
architecture-specific subdirectory).
LIBEXECDIR
LIBEXECDIR
BINDIR或通过其他方式的启动脚本或符号链接运行。默认为
libexec
BINDIR or by other means. Defaults to
libexec
INCLUDEDIR
INCLUDEDIR
include.
include.
DATAROOTDIR
DATAROOTDIR
DOCDIR.
DOCDIR.
DATADIR
DATADIR
DATAROOTDIR,并且是引用位置的首选方式。
DATAROOTDIR and is the preferred way to
refer to locations for arbitrary project data not covered by other defined
locations.
MANDIR
MANDIR
man。默认为DATAROOTDIR/man
man format. Defaults to DATAROOTDIR/man
DOCDIR
DOCDIR
DATAROOTDIR/doc/PROJECT_NAME
(请参阅下面的注释了解为什么依赖此默认值相对不安全)。
DATAROOTDIR/doc/PROJECT_NAME
(see notes below for why relying on this default value is relatively unsafe).
由于每个位置都被定义为缓存变量,因此可以根据需要覆盖它们。开发人员通常不会更改它们,因为安装位置应该在项目的控制之下。即使对于项目来说,通常也不建议更改默认位置,但如果项目希望主要遵循标准布局并且只需要进行一些小的调整,那么它可能会很有用。
Since each location is defined as a cache variable, they can be overridden if needed. Developers would not normally change them, as install locations should be under the control of the project. Even for the project though, changing the locations from the defaults is not generally advisable, but it can be useful if the project wants to mostly follow the standard layout and only needs to make a few small tweaks.
该DOCDIR位置值得特别提及,因为它默认为包含变量的值PROJECT_NAME。PROJECT_NAME由每次调用更新project(),因此在整个项目层次结构中可能会有所不同。GNUInstallDirs仅当缓存变量尚未定义时,模块才会设置缓存变量,因此 的值将由模块首次包含的CMAKE_INSTALL_DOCDIR位置确定
。GNUInstallDirs为了防止这种情况并允许默认文档目录遵循项目层次结构,项目可能希望在DOCDIR每次包含模块时显式设置位置(非缓存变量将覆盖缓存变量):
The DOCDIR location deserves special mention, as it defaults to a value that
incorporates the PROJECT_NAME variable. PROJECT_NAME is updated by each
call to project() and therefore can vary throughout the project hierarchy.
The GNUInstallDirs module sets cache variables only if they are not already
defined, so the value of CMAKE_INSTALL_DOCDIR will be determined by where the
GNUInstallDirs module is first included. To protect against this and allow
the default documentation directory to follow the project hierarchy, projects
may want to explicitly set the DOCDIR location every time the module is
included (the non-cache variable will override the cache variable):
# Explicitly set DOCDIR location each time
include(GNUInstallDirs)
set(CMAKE_INSTALL_DOCDIR
${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}
)# Explicitly set DOCDIR location each time
include(GNUInstallDirs)
set(CMAKE_INSTALL_DOCDIR
${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}
)
在本章的其余部分中,示例将使用CMAKE_INSTALL_<dir>
大多数相关安装目标的变量。
For the remainder of this chapter, examples will use the CMAKE_INSTALL_<dir>
variables for most relative install destinations.
确定安装文件的相对布局后,必须确定该布局的基本安装位置。许多考虑因素都会影响这一决定,但也许要回答的第一个问题是安装是否应该可重新定位。这仅意味着可以使用任何安装基点,并且只要保留相对布局,安装的项目仍将按预期工作。可重新定位是非常理想的,并且应该是大多数项目的目标,因为它开辟了更多用例,例如:
After the relative layout of installed files has been determined, the base install location of that layout must be decided. A number of considerations impact this decision, but perhaps the first question to answer is whether the install should be relocatable. This just means that any install base point can be used and as long as the relative layout is preserved, the installed project will still work as intended. Being relocatable is highly desirable and should be the goal of most projects, since it opens up more use cases, such as:
并非所有项目都可以重定位,有些项目需要将其文件放置在非常特定的位置(例如内核包)。除了一些配置文件之外,某些项目可以重新定位,在这种情况下,有时一个有用的策略是将这些特定文件作为脚本化的安装后步骤来处理(下一章将讨论特定打包系统的某些方面)。
Not all projects can be made relocatable, some need to place their files in very specific locations (e.g. kernel packages). Some projects can be relocatable except for a few configuration files, in which case a useful strategy can sometimes be to handle those specific files as a scripted post-install step (the next chapter discusses some aspects of this for specific packaging systems).
基本安装位置的选择与目标平台密切相关,每个平台都有自己的通用实践和指南。在 Windows 上,基本安装位置通常是 的子目录C:\Program Files,而在大多数其他系统上,它是/usr/local或 的子目录/opt。CMake 提供了许多用于管理基本安装位置的控件,以消除这些平台差异。也许最重要的是
CMAKE_INSTALL_PREFIX变量,它控制用户构建install目标时的基本安装位置(可以使用某些生成器类型调用目标INSTALL
)。默认值CMAKE_INSTALL_PREFIX是
C:\Program Files\${PROJECT_NAME}在 Windows 和/usr/local基于 Unix 的平台上。
The choice of base install location is closely tied to the target platform,
with each one having its own common practices and guidelines. On Windows, the
base install location is usually a subdirectory of C:\Program Files, whereas
on most other systems, it is /usr/local or a subdirectory of /opt. CMake
provides a number of controls for managing the base install location to mostly
abstract away these platform differences. Perhaps the most important is the
CMAKE_INSTALL_PREFIX variable, which controls the base install location
when the user builds the install target (the target may be called INSTALL
with some generator types). The default value of CMAKE_INSTALL_PREFIX is
C:\Program Files\${PROJECT_NAME} on Windows and /usr/local on Unix-based
platforms.
在 Linux 上安装时,默认值不符合文件系统层次结构标准。FHS 要求系统包使用/或的基本位置/usr,后者更有可能是所需的选择。对于附加软件包,应将它们安装到/opt/<package>或
/opt/<provider>,建议使用/opt/<provider>/<package>。如果<provider>使用 ,则正式预期它是 LANANA 注册名称或只是提供包的组织的小写完全限定域名。这是为了避免尝试使用相同基本安装位置的不同软件包之间发生冲突。
When installing on Linux, the default value does not conform to the File System
Hierarchy standard.
The FHS requires system packages to use a base location of / or /usr, with
the latter more likely to be the desired choice.
For add-on packages, they should be installed to /opt/<package> or
/opt/<provider>, with a recommendation to use /opt/<provider>/<package>.
If <provider> is used, it is formally expected to be a LANANA-registered name
or just the lowercase fully qualified domain name of the organization providing
the package.
This is to avoid clashes between different packages trying to use the same base
install location.
对于大多数项目,在非 Windows 平台上,建议显式设置
CMAKE_INSTALL_PREFIX为符合 FHS 的/opt/…路径。这通常应该只在顶层完成CMakeLists.txt,并且应该通过适当的检查来保护它,以确保该项目实际上是源树的顶层(以支持分层项目安排)。
For most projects, on non-Windows platforms it is advisable to explicitly set
CMAKE_INSTALL_PREFIX to a FHS-compliant /opt/… path.
This should generally be done only in the top level CMakeLists.txt and it
should be protected by an appropriate check that the project is in fact the top
level of the source tree (to support hierarchical project arrangements).
if(NOT WIN32 AND
CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(CMAKE_INSTALL_PREFIX
"/opt/mycompany.com/${PROJECT_NAME}"
)
endif()if(NOT WIN32 AND
CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(CMAKE_INSTALL_PREFIX
"/opt/mycompany.com/${PROJECT_NAME}"
)
endif()
对于交叉编译场景,CMAKE_STAGING_PREFIX可以定义该变量来覆盖安装规则的安装位置。这是为了允许安装到文件系统的备用部分,同时仍然保留 的所有其他效果CMAKE_INSTALL_PREFIX,例如在已安装的二进制文件中嵌入路径(在本章后面的
第 26.2.2 节“RPATH”中介绍)。CMAKE_STAGING_PREFIX也会影响大多数命令的搜索路径find_…()
。
For cross-compiling scenarios, the CMAKE_STAGING_PREFIX variable can be
defined to provide an override for where the install rule installs to. This is
to allow installing to an alternate part of the file system while still
preserving all the other effects of CMAKE_INSTALL_PREFIX, such as embedding
of paths in the installed binaries (covered in Section 26.2.2, “RPATH” later in this chapter).
CMAKE_STAGING_PREFIX also affects the search paths of most find_…()
commands.
对于某些打包场景并允许在远程位置测试安装过程,CMake 支持DESTDIR非 Windows 平台的常见功能。DESTDIR不是 CMake 变量,而是传递给构建工具或设置为环境变量以供构建工具读取的变量。它允许将安装基位置放置在某个任意位置而不是文件系统的根目录下。它通常在直接调用构建工具时在命令行上使用,例如:
For some packaging scenarios and to allow testing the install process in a
location off to the side, CMake supports the common DESTDIR functionality
for non-Windows platforms. DESTDIR is not a CMake variable, but rather it is a
variable passed to the build tool or set as an environment variable for the
build tool to read. It allows the install base location to be placed under
some arbitrary location rather than the root of the file system. It is
typically used on a command line when invoking the build tool directly, such
as:
进行 DESTDIR=/home/me/staging 安装 env DESTDIR=/home/me/staging ninja 安装
make DESTDIR=/home/me/staging install env DESTDIR=/home/me/staging ninja install
该DESTDIR功能在概念上类似于
CMAKE_STAGING_PREFIX,但DESTDIR仅在安装时指定,并且不会影响find_…()命令等内容。CMAKE_STAGING_PREFIX保存为缓存变量,而DESTDIR是环境变量,并且在构建工具的调用之间不保存。请参阅第 26.9 节“执行安装”,了解使用 CMake 3.15 或更高版本时执行安装的更灵活、更方便的方法。
The DESTDIR functionality is conceptually similar to
CMAKE_STAGING_PREFIX, but DESTDIR is specified only at install time and
does not affect things like find_…() commands. CMAKE_STAGING_PREFIX is
saved as a cache variable, whereas DESTDIR is an environment variable and is
not saved between invocations of the build tool.
See Section 26.9, “Executing An Install” for an even more flexible and more convenient
method for carrying out an install when using CMake 3.15 or later.
CMAKE_INSTALL_PREFIX、CMAKE_STAGING_PREFIX和的组合DESTDIR
使项目和开发人员可以根据需要灵活地设置基本安装位置并执行测试安装,而无需实际接触最终的预期安装位置。但请注意,各种打包格式可能有自己的默认基本安装位置,并且可能完全忽略这三个变量,而不是它们自己的特定于包的变量。
The combination of CMAKE_INSTALL_PREFIX, CMAKE_STAGING_PREFIX and DESTDIR
gives the project and the developer the flexibility to set the base install
location as needed and to perform test installs without actually touching the
final intended install location. Be aware, however, that the various packaging
formats may have their own default base install locations and may completely
ignore these three variables in preference to their own package-specific ones.
定义安装区域的结构后,现在可以将注意力转移到安装的内容本身。项目使用该install()命令来定义要安装的内容、这些内容应位于何处等等。该命令有多种不同的形式,每种形式都专注于由该命令的第一个参数指定的特定类型的实体。关键形式之一是安装项目提供的目标(而不是项目外部提供的导入目标):
With the structure of the install area defined, attention can now move to the
installed content itself. Projects use the install() command to define
what to install, where those things should be located and so on. This command
has a number of different forms, each focused on a particular type of entity
which is specified by the first argument to the command.
One of the key forms is for installing targets provided by the project (as
opposed to imported targets provided by
something external to the project):
install(TARGETS targets...
[EXPORT exportName]
[CONFIGURATIONS configs...]
[RUNTIME_DEPENDENCIES runtimeDepArgs... | ①
RUNTIME_DEPENDENCY_SET runtimeSetName]
# One or more blocks of the following
[ [entityType]
[DESTINATION dir] ②
[PERMISSIONS permissions...]
[NAMELINK_ONLY | NAMELINK_SKIP]
[COMPONENT component]
[NAMELINK_COMPONENT component] ③
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
]...
# Special case
[INCLUDES DESTINATION incDirs...]
)install(TARGETS targets...
[EXPORT exportName]
[CONFIGURATIONS configs...]
[RUNTIME_DEPENDENCIES runtimeDepArgs... | ①
RUNTIME_DEPENDENCY_SET runtimeSetName]
# One or more blocks of the following
[ [entityType]
[DESTINATION dir] ②
[PERMISSIONS permissions...]
[NAMELINK_ONLY | NAMELINK_SKIP]
[COMPONENT component]
[NAMELINK_COMPONENT component] ③
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
]...
# Special case
[INCLUDES DESTINATION incDirs...]
)
RUNTIME_…依赖项选项需要 CMake 3.21 或更高版本。RUNTIME_… dependency options require CMake 3.21 or later.DESTINATION对于 CMake 3.13 及更早版本是必需的。DESTINATION is mandatory for CMake 3.13 and earlier.NAMELINK_COMPONENT需要 CMake 3.12 或更高版本。NAMELINK_COMPONENT requires CMake 3.12 or later.targets提供一个或多个块,然后这些entityType块指定如何处理安装这些目标的各个部分。在 CMake 3.12 或更早版本中,每个目标都必须在与命令相同的目录范围中定义
install(),但 CMake 3.13 删除了此限制。对于所有 CMake 版本,entityType必须是以下之一:
One or more targets are provided and then the entityType blocks specify how
to handle installing the various parts of those targets. With CMake 3.12 or
earlier, each of the targets must be defined in the same directory scope as the
install() command, but CMake 3.13 removed this restriction. For all CMake
versions, the entityType must be one of the following:
RUNTIME
RUNTIME
LIBRARY
LIBRARY
ARCHIVE
ARCHIVE
.lib)部分。Apple 框架不包括在内。
.lib) part of shared library targets. Apple
frameworks are excluded.
OBJECTS
OBJECTS
FRAMEWORK
FRAMEWORK
POST_BUILD
自定义规则)。
POST_BUILD
custom rules).
BUNDLE
BUNDLE
PUBLIC_HEADER
PUBLIC_HEADER
PUBLIC_HEADER属性中列出的文件。在 Apple 平台上,这些头文件被作为FRAMEWORK实体类型的一部分进行处理,但对于非 Apple 平台,此类目标被视为普通共享库,并且需要将头文件显式安装为单独的实体类型。
PUBLIC_HEADER property. On Apple platforms,
these header files are handled as part of the FRAMEWORK entity type instead,
but for non-Apple platforms, such targets are treated as ordinary shared
libraries and the headers need to be explicitly installed as a separate entity
type.
PRIVATE_HEADER
PRIVATE_HEADER
PUBLIC_HEADER实体类型类似,不同之处在于受影响的目标属性是PRIVATE_HEADER。
PUBLIC_HEADER entity type, except the
affected target property is PRIVATE_HEADER.
RESOURCE
RESOURCE
RESOURCE属性中列出的文件。在 Apple 平台上,它们作为框架或捆绑包的一部分安装。
RESOURCE property.
On Apple platforms, they are installed as part of the framework or bundle
instead.
在 后entityType,可以列出各种选项,它们仅适用于该实体类型。例如,下面显示了如何安装库,将各个部分放在所有平台上的预期位置(假设它们不是 Apple 框架):
After the entityType, various options can be listed and they only apply
to that entity type. For instance, the following shows how to install libraries
in a way that puts the respective parts in their expected place on all
platforms (assuming they are not Apple frameworks):
install(TARGETS MySharedLib MyStaticLib
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)install(TARGETS MySharedLib MyStaticLib
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
上面的示例显示了该DESTINATION选项如何为同一目标的不同部分指定不同的位置。该命令足够灵活,可以同时处理不同类型的多个目标。对于MySharedLib,在 Windows 上,DLL 将转到RUNTIME目标,导入库将转到ARCHIVE目标。在其他平台上,共享库将安装到LIBRARY
目标位置。目标的静态库MyStaticLib将安装到
ARCHIVE目的地。
The above example shows how the DESTINATION option can specify different
locations for different parts of the same target.
The command is flexible enough to handle multiple targets of different types
all at once.
For MySharedLib, on Windows the DLL would go to the RUNTIME destination
and the import library to the ARCHIVE destination.
On other platforms, the shared library would be installed to the LIBRARY
destination.
The static library of the MyStaticLib target would be installed to the
ARCHIVE destination.
如果目标提供了没有相应entityType部分的特定实体(例如,目标之一是静态库,但未ARCHIVE提供任何部分),CMake 通常会发出警告或错误。作为例外,可以entityType省略 ,在这种情况下,目标列表后面的选项将应用于所有实体类型。通常只有当列出的目标明显只能有一种实体类型时才会执行此操作:
CMake will usually issue a warning or error if a target provides a particular
entity for which there is no corresponding entityType section (e.g. one of
the targets is a static library but no ARCHIVE section is provided). As an
exception to this, the entityType can be omitted, in which case the options
that follow the list of targets will apply to all entity types. This is usually
only done when it is obvious that there can only be one entity type for the
targets listed:
# Targets are both executables, so specifying the entity
# type isn't needed
install(TARGETS exe1 exe2
DESTINATION ${CMAKE_INSTALL_BINDIR}
)# Targets are both executables, so specifying the entity
# type isn't needed
install(TARGETS exe1 exe2
DESTINATION ${CMAKE_INSTALL_BINDIR}
)
对于 CMake 3.13 及更早版本,DESTINATION必须提供 a。CMake 3.14 放宽了这一要求,允许可执行文件、静态库和共享库的默认目标,但不允许模块库、Apple 捆绑包或框架。附加到库目标的公共和私有标头也有可用的默认目标。CMAKE_INSTALL_…对于支持的目标类型,默认目标由模块为这些实体提供的相同变量给出
GNUInstallDirs。如果没有定义这样的变量(即GNUInstallDirs尚未包含该模块),
GNUInstallDirs则将使用大多数遵循与模块相同的默认值的硬编码默认值。请注意,硬编码默认值缺乏处理各种 Linux/Unix 发行版之间细微差异的逻辑,因此项目通常应包含该GNUInstallDirs模块以获得最广泛的平台支持。该命令的 CMake 文档详细介绍了硬编码默认值
install()。
For CMake 3.13 and earlier, a DESTINATION must be provided.
CMake 3.14 relaxed this requirement, allowing default destinations for
executables, static libraries and shared libraries, but not module libraries,
Apple bundles or frameworks.
Public and private headers attached to library targets also have default
destinations available.
For the supported target types, the default destinations are given by the same
CMAKE_INSTALL_… variables that the GNUInstallDirs module provides for
those entities.
If no such variables are defined (i.e. the GNUInstallDirs module has not been
included), hard-coded defaults that mostly follow the same defaults as the
GNUInstallDirs module will be used instead.
Note that the hard-coded defaults lack the logic for handling subtle
differences across various Linux/Unix distributions, so projects should
generally include the GNUInstallDirs module to obtain the broadest platform
support.
The hard-coded defaults are detailed in the CMake documentation for the
install() command.
include(GNUInstallDirs)
# Only legal with CMake 3.14 or later
install(TARGETS MyExe MySharedLib MyStaticLib)include(GNUInstallDirs)
# Only legal with CMake 3.14 or later
install(TARGETS MyExe MySharedLib MyStaticLib)
上述内容将安装MyExe到CMAKE_INSTALL_BINDIR、MyStaticLib和
CMAKE_INSTALL_LIBDIR到MySharedLib这两个位置之一或两个位置,具体取决于平台。附加到这些目标的任何公共或私有标头都将安装到
CMAKE_INSTALL_INCLUDEDIR. 虽然这非常方便并且使安装命令非常简洁,但它迫使项目需要 CMake 3.14 作为其最低版本。在本章的其余部分中,大多数示例中仍然明确给出了目标,以便它们仍然适用于最广泛的 CMake 版本。
The above would install MyExe to CMAKE_INSTALL_BINDIR, MyStaticLib to
CMAKE_INSTALL_LIBDIR and MySharedLib to one or both of those two locations
depending on the platform.
Any public or private headers attached to these targets would be installed to
CMAKE_INSTALL_INCLUDEDIR.
While this can be very convenient and make the install command very concise,
it forces the project to require CMake 3.14 as its minimum version.
For the remainder of this chapter, the destinations are still explicitly given
in most examples so that they remain applicable to the broadest range of
CMake versions.
实体类型后面的选项不仅可以指定目的地。他们还可以使用选项覆盖默认权限PERMISSIONS,指定一个或多个与第 19.2 节“复制文件”file(COPY)中描述的命令相同的值:
Options following an entity type can specify more than just the destination.
They can also override the default permissions with the PERMISSIONS option,
specifying one or more of the same values as for the file(COPY) command
described back in Section 19.2, “Copying Files”:
|
|
|
|
|
|
|
|
|
|
|
至于file(COPY),平台不支持的权限将被忽略。请注意,CMake 通常默认为所有目标设置适当的权限。通常,仅当安装位置需要比正常情况更多的限制性权限或者需要添加其中一项权限SETUID时,才需要显式提供权限。SETGID以下示例演示了这两种情况。
As for file(COPY), permissions not supported for the platform will simply be
ignored.
Note that CMake usually sets appropriate permissions for all targets by default.
One would typically only need to explicitly provide permissions if the
installed location needs more restrictive permissions than normal or if
one of the SETUID or SETGID permissions needs to be added.
The following example demonstrates both scenarios.
# Intended to only be run by an administrator,
# so only allow the owner to have access
install(TARGETS OnlyOwnerCanRunMe
DESTINATION ${CMAKE_INSTALL_SBINDIR}
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
)
# Install with set-group permission
install(TARGETS RunAsGroup
DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE SETGID
)# Intended to only be run by an administrator,
# so only allow the owner to have access
install(TARGETS OnlyOwnerCanRunMe
DESTINATION ${CMAKE_INSTALL_SBINDIR}
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
)
# Install with set-group permission
install(TARGETS RunAsGroup
DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE SETGID
)
对于LIBRARY实体类型,某些平台支持在为库目标提供版本详细信息时创建符号链接(请参阅
第 21.3 节 “共享库版本控制”)。共享库可能存在的文件和符号链接集通常如下所示:
For the LIBRARY entity type, some platforms support the creation of symbolic
links when version details have been provided for a library target (see
Section 21.3, “Shared Library Versioning”). The set of files and symlinks that might exist
for a shared library typically look something like this:
libMyShared.so.1.3.2 ① libMyShared.so.1 --> libMyShared.so.1.3.2 ② libMyShared.so --> libMyShared.so.1 ③
libMyShared.so.1.3.2 ① libMyShared.so.1 --> libMyShared.so.1.3.2 ② libMyShared.so --> libMyShared.so.1 ③
-lMyShared。-lMyShared.安装LIBRARY实体时,
可以给出NAMELINK_ONLY或选项。NAMELINK_SKIP该NAMELINK_ONLY选项将导致仅安装名称链接,而NAMELINK_SKIP将导致安装除名称链接之外的所有内容。如果库目标没有版本详细信息或平台不支持名称链接,则这两个选项的行为会发生变化。
NAMELINK_ONLY然后将不安装任何内容并NAMELINK_SKIP安装真正的库。当创建单独的运行时和开发包时,这些选项特别有用,名称链接部分进入开发包,其他文件/链接进入运行时包。
When installing LIBRARY entities, the NAMELINK_ONLY or NAMELINK_SKIP
options can be given. The NAMELINK_ONLY option will result in only the
namelink being installed, whereas NAMELINK_SKIP will result in all but the
namelink being installed. If a library target has no version details or the
platform doesn’t support namelinks, the behavior of these two options changes.
NAMELINK_ONLY will then install nothing and NAMELINK_SKIP will install the
real library. These options are especially useful when creating separate
runtime and development packages, with the namelink part going into the
development package and the other files/links going into the runtime package.
当给出一个NAMELINK_ONLY选项时,CMake 不会警告该命令中未提及的库其他部分缺少实体类型块
install()。这是必需的,因为NAMELINK_SKIP和NAMELINK_ONLY
不能在同一调用中同时给出install(),两者必须在单独的调用中分开(参见下面的示例)。
When a NAMELINK_ONLY option is given, CMake will not warn about missing
entity type blocks for other parts of the library not mentioned in that
install() command. This is needed because NAMELINK_SKIP and NAMELINK_ONLY
cannot both be given in the same install() call, the two have to be split
across separate calls (see example below).
每个entityType部分还可以指定一个COMPONENT选项。组件是主要用于打包的逻辑分组,将在下一章详细讨论,但现在,将它们视为分离不同安装集的一种方式。上述单独运行时和开发包的场景可以设置如下:
Each entityType section can also specify a COMPONENT option. Components are
a logical grouping used mainly for packaging and are discussed in detail in the
next chapter, but for now, think of them as a way of separating out different
install sets. The above mentioned scenario for separate runtime and development
packages could be set up as follows:
install(TARGETS MyShared MyStatic
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT MyProj_Runtime
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
NAMELINK_SKIP
COMPONENT MyProj_Runtime
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT MyProj_Development
)
# Because NAMELINK_ONLY is given, CMake won't complain
# about a missing RUNTIME block
install(TARGETS MyShared
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
NAMELINK_ONLY
COMPONENT MyProj_Development
)install(TARGETS MyShared MyStatic
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT MyProj_Runtime
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
NAMELINK_SKIP
COMPONENT MyProj_Runtime
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT MyProj_Development
)
# Because NAMELINK_ONLY is given, CMake won't complain
# about a missing RUNTIME block
install(TARGETS MyShared
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
NAMELINK_ONLY
COMPONENT MyProj_Development
)
从 CMake 3.12 开始,可以使用该选项将名称链接拆分到不同组件的更简单方法NAMELINK_COMPONENT。此选项可以与 结合使用COMPONENT,但只能在LIBRARY块内使用。使用这个新选项,可以更简洁地表达上面的内容:
From CMake 3.12, a simpler way of splitting out the namelink to a different
component is available using the NAMELINK_COMPONENT option. This option can
be used in conjunction with COMPONENT, but only within a LIBRARY block.
Using this new option, the above can be expressed more concisely:
install(TARGETS MyShared MyStatic
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT MyProj_Runtime
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT MyProj_Runtime
NAMELINK_COMPONENT MyProj_Development ①
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT MyProj_Development
)install(TARGETS MyShared MyStatic
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT MyProj_Runtime
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT MyProj_Runtime
NAMELINK_COMPONENT MyProj_Development ①
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT MyProj_Development
)
NAMELINK_COMPONENT需要 CMake 3.12 或更高版本。NAMELINK_COMPONENT requires CMake 3.12 or later.如果COMPONENT块没有给出,则它与一个默认组件相关联,该组件的名称由变量 给出
CMAKE_INSTALL_DEFAULT_COMPONENT_NAME。如果未设置该变量,
Unspecified则用作默认组件名称。更改默认组件名称会很有帮助的一个示例是第三方子项目不使用任何安装组件。为了使子项目的安装工件与主项目分开,可以在调用将add_subdirectory()子项目拉入主构建之前更改默认名称。
If no COMPONENT is given for a block, it is associated with a default
component whose name is given by the variable
CMAKE_INSTALL_DEFAULT_COMPONENT_NAME. If that variable is not set,
Unspecified is used as the default component name. An example where it can be
helpful to change the default component name is where a third party child
project doesn’t use any install components. To keep that child project’s
install artifacts separate from the main project, the default name can be
changed just before calling add_subdirectory() to pull the child project into
the main build.
该EXCLUDE_FROM_ALL选项可用于限制实体块仅针对特定于组件的安装进行安装。默认情况下,安装不是特定于组件的,并且会安装所有组件,但打包实现可能会单独安装特定组件。CMake 3.12 中添加了文档,以展示如何从命令行执行此操作。对于大多数项目来说,EXCLUDE_FROM_ALL不太可能需要。
The EXCLUDE_FROM_ALL option can be used to restrict an entity block to only
get installed for component-specific installs. By default, an
install is not component-specific and all components are installed, but
packaging implementations may install specific components individually.
Documentation was added in CMake 3.12 to show how to do this from the command
line as well. For most projects, EXCLUDE_FROM_ALL is unlikely to be needed.
该OPTIONAL关键字也很少使用。如果预期目标的实体类型存在但缺少(例如,实体ARCHIVE类型部分的 Windows DLL 的导入库),CMake 不会将其视为错误。请谨慎使用此选项,因为它能够掩盖构建/安装逻辑的错误配置。
The OPTIONAL keyword is also rarely used. If the entity type of a target is
expected to be present but it is missing (e.g. the import library of a Windows
DLL for an ARCHIVE entity type section), CMake will not consider it an error.
Use this option with caution, as it has the ability to mask misconfiguration of
the build/install logic.
实体类型块还可以通过
CONFIGURATIONS向其添加选项来使其特定于配置。仅当当前构建类型是列出的类型之一时才会安装该实体类型。对于单个命令,不能多次列出实体类型install(),因此如果不同的配置需要不同的详细信息,则需要多次调用。以下示例展示了如何在不同目录中安装 Debug 和 Release 版本的静态库:
An entity type block can also be made configuration-specific by adding a
CONFIGURATIONS option to it. That entity type will only be installed if the
current build type is one of those listed. An entity type cannot be listed more
than once for a single install() command, so if different configurations
need different details, multiple calls are needed. The following example shows
how to install the Debug and Release versions of static libraries in different
directories:
install(TARGETS MyStatic
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}/Debug
CONFIGURATIONS Debug
)
install(TARGETS MyStatic
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}/Release
CONFIGURATIONS Release RelWithDebInfo MinSizeRel
)install(TARGETS MyStatic
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}/Debug
CONFIGURATIONS Debug
)
install(TARGETS MyStatic
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}/Release
CONFIGURATIONS Release RelWithDebInfo MinSizeRel
)
该CONFIGURATIONS关键字还可以位于所有实体块之前,并充当那些不提供自己的配置覆盖的默认实体块。在以下示例中,所有块仅针对构建安装Release,但为和ARCHIVE安装的块除外。DebugRelease
The CONFIGURATIONS keyword can also precede all entity blocks and act as a
default for those that don’t provide their own configuration override.
In the following example, all blocks get installed only for Release builds,
except for the ARCHIVE block which is installed for Debug and Release.
install(TARGETS MyShared MyStatic
CONFIGURATIONS Release
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}
CONFIGURATIONS Debug Release
)install(TARGETS MyShared MyStatic
CONFIGURATIONS Release
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}
CONFIGURATIONS Debug Release
)
有关如何使用和
关键字,请参见第 26.7.1 节“运行时依赖性集”。RUNTIME_DEPENDENCIESRUNTIME_DEPENDENCY_SET
See Section 26.7.1, “Runtime Dependency Sets” for how to use the RUNTIME_DEPENDENCIES and
RUNTIME_DEPENDENCY_SET keywords.
如果目标被导出(在下面第 26.3 节“安装导出”中进一步讨论),它们就有机会设置由其他项目的目标使用的接口属性。各种INTERFACE目标属性会自动传递到已安装目标的导出详细信息,但需要进行特殊处理来考虑构建目标与使用已安装目标的明显不同的需求。考虑以下代码示例:
If the targets are exported (discussed in Section 26.3, “Installing Exports” further
below), they have the opportunity to set interface properties to be consumed by
other projects’ targets. The various INTERFACE target properties are carried
through to the exported details of the installed target automatically, but
special handling is needed to account for the distinctly different needs of
building the target versus those for consuming the installed target. Consider
the following code sample:
add_library(Foo STATIC ...)
target_include_directories(Foo
INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/somewhere
${MyProject_BINARY_DIR}/anotherDir
)
install(TARGETS Foo DESTINATION ...)add_library(Foo STATIC ...)
target_include_directories(Foo
INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/somewhere
${MyProject_BINARY_DIR}/anotherDir
)
install(TARGETS Foo DESTINATION ...)
在构建中,任何链接到的内容Foo都将具有其标头搜索路径somewhere并anotherDir添加到其标头搜索路径中的绝对路径。安装后Foo,它可能会被打包并部署到完全不同的机器上。somewhere显然,和的路径anotherDir不再有意义,但上面的示例无论如何都会将它们添加到消费目标的标头搜索路径中。需要的是一种表达“xxx构建时使用路径,
yyy安装时使用路径”的方式,这正是BUILD_INTERFACE和
INSTALL_INTERFACE生成器表达式的作用:
Within the build, anything linking to Foo will have the absolute paths
to somewhere and anotherDir added to its header search path. When Foo is
installed, it may be packaged up and deployed to an entirely different machine.
Clearly the path to somewhere and anotherDir would no longer make sense,
but the above example would add them to consuming targets’ header search path
anyway. What is needed is a way to say "Use path xxx when building and path
yyy when installing", which is exactly what the BUILD_INTERFACE and
INSTALL_INTERFACE generator expressions do:
include(GNUInstallDirs)
set(somewhere ${CMAKE_CURRENT_BINARY_DIR}/somewhere)
set(anotherDir ${MyProject_BINARY_DIR}/anotherDir)
target_include_directories(Foo
INTERFACE
$<BUILD_INTERFACE:${somewhere}>
$<BUILD_INTERFACE:${anotherDir}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)include(GNUInstallDirs)
set(somewhere ${CMAKE_CURRENT_BINARY_DIR}/somewhere)
set(anotherDir ${MyProject_BINARY_DIR}/anotherDir)
target_include_directories(Foo
INTERFACE
$<BUILD_INTERFACE:${somewhere}>
$<BUILD_INTERFACE:${anotherDir}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
$<BUILD_INTERFACE:xxx>将扩展为xxx构建树并在安装时扩展为空,而$<INSTALL_INTERFACE:yyy>相反,确保yyy仅为已安装的目标添加。在 的情况下
INSTALL_INTERFACE,yyy通常是相对路径,被视为相对于基本安装位置。
$<BUILD_INTERFACE:xxx> will expand to xxx for the build tree and expand to
nothing when installing, whereas $<INSTALL_INTERFACE:yyy> does the opposite,
ensuring that yyy is only added for the installed target. In the case of
INSTALL_INTERFACE, yyy is usually a relative path, which is treated as
being relative to the base install location.
虽然构建树中的标头搜索路径可能因目标而异,但安装后目标通常会共享相同的标头搜索路径。在上面的示例中,CMAKE_INSTALL_INCLUDEDIR可能会为每个目标重复,但为每个目标单独指定并不是最方便的方法。可以使用INCLUDES该命令的选项来
为一组目标指定相同的信息。install()之后给出的所有目录都
INCLUDES DESTINATION将添加到INTERFACE_INCLUDE_DIRECTORIES
列出的每个已安装目标的属性中。这使得标头搜索路径的描述更加简洁。
While the header search path within the build tree may vary from target to
target, it is common for the targets to share the same header search
path once installed. In the above example, CMAKE_INSTALL_INCLUDEDIR is
likely to be repeated for every target, but specifying it
individually for each one is not the most convenient approach. The
INCLUDES option of the install() command can be used instead to specify the
same information for a group of targets. All the directories given after
INCLUDES DESTINATION are added to the INTERFACE_INCLUDE_DIRECTORIES
property of each installed target listed.
This leads to a more concise description of header search paths.
add_library(MyStatic STATIC ...)
add_library(MyHeaderOnly INTERFACE ...)
set(incDir ${CMAKE_CURRENT_BINARY_DIR}/static_exports)
target_include_directories(MyStatic
PUBLIC $<BUILD_INTERFACE:${incDir}>
)
target_include_directories(MyHeaderOnly
INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
install(TARGETS MyStatic MyHeaderOnly
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)add_library(MyStatic STATIC ...)
add_library(MyHeaderOnly INTERFACE ...)
set(incDir ${CMAKE_CURRENT_BINARY_DIR}/static_exports)
target_include_directories(MyStatic
PUBLIC $<BUILD_INTERFACE:${incDir}>
)
target_include_directories(MyHeaderOnly
INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
install(TARGETS MyStatic MyHeaderOnly
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
与其他实体类型块不同,如果需要,可以列出多个目录
INCLUDES DESTINATION,尽管这在实践中可能不太常见。另请注意,一个INCLUDES块不支持其他块支持的其他详细信息entityType,它可能只指定一个
DESTINATION关键字,后跟一个或多个位置。
Unlike the other entity type blocks, multiple directories can be listed for
INCLUDES DESTINATION if required, although this is likely to be less common
in practice. Also note that an INCLUDES block supports none of the other
details that other entityType blocks support, it may only specify a
DESTINATION keyword followed by one or more locations.
当操作系统加载库或可执行文件时,它必须找到该二进制文件所链接的所有其他共享库。不同的平台有不同的处理方式。Windows 通过搜索PATH环境变量中的位置以及二进制文件所在的目录来查找所有必需的库。其他平台结合其他机制(例如文件中列出的库)使用专门用于该目的的不同环境变量(例如LD_LIBRARY_PATH或其变体)conf。依赖环境变量的一个缺点是它依赖于加载二进制文件的人员或进程来正确设置环境。
When a library or executable is loaded by the operating system, it has to find
all the other shared libraries the binary has been linked against. Different
platforms have different ways of handling this. Windows relies on finding all
required libraries by searching the locations in the PATH environment
variable as well as the directory in which the binary is located. Other
platforms use different environment variables specifically intended for the
purpose, such as LD_LIBRARY_PATH or variations thereof, in conjunction with
other mechanisms such as libraries listed in conf files. A drawback to the
dependence on environment variables is that it relies on the person or process
loading the binary to have set up the environment correctly.
在许多情况下,提供二进制文件的包已经知道可以在哪里找到许多依赖库,因为它们可能是同一包的一部分。大多数非 Windows 平台支持二进制文件,能够将库搜索路径直接编码到二进制文件本身中。此功能的通用名称是运行路径或RPATH支持,但实际名称可能具有特定于平台的变体。通过嵌入的RPATH详细信息,二进制文件可以是独立的,而不必依赖于环境或系统配置提供的任何路径。此外, anRPATH可以包含某些占位符,使其能够有效地定义仅在运行时解析为绝对路径的相对路径。占位符允许根据二进制文件的位置进行解析,因此可重定位包可以定义RPATH仅基于包的相对布局的硬编码路径的详细信息。
In many cases, the package providing the binary already knows where many of the
dependent libraries can be found, since they may have been part of the same
package. Most non-Windows platforms support binaries being able to encode
library search paths directly into the binaries themselves. The generic name
for this feature is run path or RPATH support, although the actual name
may have platform-specific variations. With embedded RPATH details, a binary
can be self-contained and not have to rely on any paths being provided by the
environment or system configuration. Furthermore, an RPATH can contain
certain placeholders that allow it to effectively define relative paths that
are only resolved to absolute paths at run time. The placeholders allow that
resolution to be made based on the location of the binary, so relocatable
packages can define RPATH details that only hard-code paths based on the
package’s relative layout.
正如上一节中接口属性的情况一样,RPATH构建树和已安装的二进制文件存在冲突的需求。在构建树中,开发人员需要二进制文件能够找到它们链接到的共享库,以便可以运行可执行文件(例如用于调试、测试执行等)。在支持 的平台上RPATH,CMake 会默认嵌入所需的路径,从而为开发者提供最便捷的体验,无需任何进一步的设置。不过,这些RPATH详细信息仅适用于特定的构建树,因此当安装目标时,CMake 会使用替换路径重写它们(默认替换会生成空RPATH)。
Just as was the case for interface properties in the previous section, there
are conflicting needs for RPATH in the build tree and for installed binaries.
In the build tree, developers need the binaries to be able to find the shared
libraries they link to so that executables can be run (e.g. for debugging, test
execution and so on). On platforms that support RPATH, CMake will embed the
required paths by default, thereby giving developers the most convenient
experience without requiring any further setup. These RPATH details are only
suitable for that particular build tree though, so when the targets are
installed, CMake rewrites them with replacement paths (the default replacement
yields an empty RPATH).
默认值RPATH是一个合理的起点,但它们不太可能适合已安装的目标。项目将希望覆盖默认行为,以确保构建树和安装的场景都得到适当的满足。CMake 允许单独控制构建和安装RPATH
位置,因此项目可以实施最适合其需求的策略。以下目标属性和变量可用于影响
RPATH行为:
The RPATH defaults are a reasonable starting point, but they are unlikely to
be suitable for installed targets. Projects will want to override the default
behavior to ensure that both build tree and installed scenarios are suitably
catered for. CMake allows separate control of the build and install RPATH
locations, so projects can implement a strategy that best fits their needs. The
following target properties and variables can be useful for influencing the
RPATH behavior:
BUILD_RPATH
BUILD_RPATH
dlopen(),例如加载可选插件模块时。该属性由或CMAKE_BUILD_RPATH创建目标时变量
的值初始化
。虽然 CMake 长期以来一直支持自动添加路径,但属性和
变量仅在 CMake 3.8 中添加。add_library()add_executable()BUILD_RPATHCMAKE_BUILD_RPATH
dlopen() or some equivalent mechanism, such as when loading
optional plugin modules. This property is initialized by the value of the
CMAKE_BUILD_RPATH variable at the time the target is created by
add_library() or add_executable(). While the automatically added paths have
been supported in CMake for a long time, the BUILD_RPATH property and the
CMAKE_BUILD_RPATH variable were only added in CMake 3.8.
BUILD_RPATH_USE_ORIGIN
BUILD_RPATH_USE_ORIGIN
$ORIGINan 的
平台RPATH(请参阅下文),将此属性设置为 true 会导致 CMake 嵌入$ORIGIN相对路径,而不是构建树二进制文件中的绝对路径。这是为了促进能够进行可重复、可重定位的构建。的初始值BUILD_RPATH_USE_ORIGIN取自CMAKE_BUILD_RPATH_USE_ORIGIN创建目标时变量的值。该属性只会影响 CMake 自动确定的路径,不会影响该
BUILD_RPATH属性中指定的任何路径。它也不会对
RPATH已安装的二进制文件的嵌入产生任何影响。
$ORIGIN in an
RPATH (see further below), setting this property to true results in CMake
embedding $ORIGIN-relative paths rather than absolute paths in the build
tree’s binaries. This is to facilitate being able to make reproducible,
relocatable builds. The initial value for BUILD_RPATH_USE_ORIGIN is taken
from the value of the CMAKE_BUILD_RPATH_USE_ORIGIN variable at the time
the target is created. This property will only affect those paths CMake
determines automatically, it will not affect any paths specified in the
BUILD_RPATH property. It will also not have any effect on the embedded
RPATH of installed binaries.
INSTALL_RPATH
INSTALL_RPATH
RPATH安装二进制文件时的位置。与 build 不同RPATH,CMake 默认不提供任何安装RPATH内容,因此项目应将此属性设置为反映已安装布局的路径列表。下面进一步详细讨论如何做到这一点。CMAKE_INSTALL_RPATH创建目标时,该属性由变量的值初始化
。
RPATH of the binary
when it is installed. Unlike the build RPATH, CMake does not provide any
install RPATH contents by default, so the project should set this property to
a list of paths that reflect the installed layout. Details further below
discuss how this can be done. This property is initialized by the value of the
CMAKE_INSTALL_RPATH variable when the target is created.
INSTALL_RPATH_USE_LINK_PATH
INSTALL_RPATH_USE_LINK_PATH
RPATH,但前提是该路径指向项目源目录和二进制目录之外的位置。这主要用于嵌入外部库的绝对路径,这些外部库不是项目的一部分,但预计位于项目将部署到的所有计算机上的同一位置。请谨慎使用此选项,因为此类假设可能会降低已安装软件包的稳健性(路径可能会随着外部库的未来版本而变化,系统管理员可能会选择非默认安装配置等)。CMAKE_INSTALL_RPATH_USE_LINK_PATH创建目标时,该属性由变量的值初始化。
RPATH locations, but only if the path points to a location outside the
project’s source and binary directories. This is mainly useful for embedding
absolute paths to external libraries that are not part of the project, but that
are expected to be at the same location on all machines the project will be
deployed to. Use this with caution, as such assumptions can reduce the
robustness of the installed package (paths may change with future releases of
the external libraries, system administrators may choose non-default
installation configurations, etc.). This property is initialized by the
value of the CMAKE_INSTALL_RPATH_USE_LINK_PATH variable when the target
is created.
BUILD_WITH_INSTALL_RPATH
BUILD_WITH_INSTALL_RPATH
RPATH也可能适合构建树。RPATH通过将此目标属性设置为 true,将不使用构建,而是RPATH在构建时将安装嵌入到二进制文件中。请注意,如果使用加载程序支持的占位符而不是链接器支持的占位符,这可能会在链接过程中导致问题(如下所述)。CMAKE_BUILD_WITH_INSTALL_RPATH
创建目标时,该属性由变量初始化。
RPATH may also be suitable for the build tree.
By setting this target property to true, the build RPATH is not used and the
install RPATH will be embedded in the binary at build time instead.
Note that this may cause problems during linking if using placeholders
supported by the loader but not the linker (discussed below).
This property is initialized by the CMAKE_BUILD_WITH_INSTALL_RPATH
variable when the target is created.
SKIP_BUILD_RPATH
SKIP_BUILD_RPATH
RPATH设置任何构建。BUILD_RPATH将被忽略,并且 CMake 不会自动添加RPATH目标链接到的库的条目。请注意,如果依赖库链接到其他库,这可能会导致构建失败,因此请谨慎使用。CMAKE_SKIP_BUILD_RPATH创建目标时,该属性由变量的值初始化
。BUILD_WITH_INSTALL_RPATH如果该属性设置为 true,它也会被覆盖。
RPATH is set. BUILD_RPATH will be ignored and CMake will not automatically
add RPATH entries for libraries the target links to. Note that this can cause
builds to fail if dependent libraries link to other libraries, so use with
caution. This property is initialized by the value of the
CMAKE_SKIP_BUILD_RPATH variable when the target is created. It is also
overridden by BUILD_WITH_INSTALL_RPATH if that property is set to true.
CMAKE_SKIP_INSTALL_RPATH
CMAKE_SKIP_INSTALL_RPATH
CMAKE_SKIP_BUILD_RPATH。将其设置为 true 会导致INSTALL_RPATH目标属性被忽略,并且可能会导致安装的目标在运行时无法找到其依赖库,因此其有用性值得怀疑。请注意,没有SKIP_INSTALL_RPATH目标属性,只有CMAKE_SKIP_INSTALL_RPATH变量。
CMAKE_SKIP_BUILD_RPATH. Setting it to true causes INSTALL_RPATH target
properties to be ignored and will likely cause the installed targets to fail
to find their dependent libraries at run time, so its usefulness is
questionable. Note that there is no SKIP_INSTALL_RPATH target property, only
the CMAKE_SKIP_INSTALL_RPATH variable.
CMAKE_SKIP_RPATH
CMAKE_SKIP_RPATH
RPATH禁用所有支持,并且所有上述属性和变量都将被忽略。通常不希望这样做,除非项目正在管理以其他方式加载自身的运行时库,但一般来说,该RPATH
功能通常应该是首选。
RPATH support
to be disabled and all of the above properties and variables will be ignored.
It is generally not desirable to do this unless the project is managing the run
time library loading itself in some other way, but in general the RPATH
functionality should generally be preferred.
理想情况下,安装RPATH位置应基于相对路径。在大多数基于 Unix 的平台上,这是通过使用$ORIGIN占位符来表示嵌入的二进制文件的位置来实现的RPATH。例如,以下是RPATH为项目定义安装详细信息的常用方法,这些项目遵循与
GNUInstallDirs模块定义的布局类似的布局:
Install RPATH locations should ideally be based on relative paths. This is
achieved on most Unix-based platforms by using the $ORIGIN placeholder to
represent the location of the binary in which the RPATH is embedded. For
example, the following is a common way of defining install RPATH details for
projects that follow a similar layout to that defined by the
GNUInstallDirs module:
set(CMAKE_INSTALL_RPATH $ORIGIN $ORIGIN/../lib)set(CMAKE_INSTALL_RPATH $ORIGIN $ORIGIN/../lib)
为了使其更加稳健并考虑到默认布局的潜在变化,还需要做更多的工作。必须计算出从可执行文件目录到库目录的相对路径,可以通过以下方式实现:
To make this more robust and account for potential changes from the default layout, a little more work is needed. One has to work out the relative path from the executables directory to the libraries directory, which can be achieved as follows:
include(GNUInstallDirs)
file(RELATIVE_PATH relDir
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}
)
set(CMAKE_INSTALL_RPATH $ORIGIN $ORIGIN/${relDir})include(GNUInstallDirs)
file(RELATIVE_PATH relDir
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}
)
set(CMAKE_INSTALL_RPATH $ORIGIN $ORIGIN/${relDir})
上述之后定义的所有目标都将具有一个INSTALL_RPATH指示加载程序在与二进制文件相同的目录中查找的目录,以及相../lib对于二进制文件位置的类似或其平台等效内容。因此,对于安装到 的可执行文件bin和安装到 的共享库
lib,这将确保两者都可以找到该项目提供的任何其他库。强烈建议将其作为首次向项目添加支持时的起点
RPATH。请注意,Apple 目标的工作方式略有不同,并且可能具有相当不同的布局,因此需要进一步调整上述内容以覆盖该平台(在下一节中讨论)。
All targets defined after the above will have an INSTALL_RPATH that directs
the loader to look in the same directory as the binary as well as something
like ../lib or its platform equivalent relative to the binary’s location.
Thus, for executables installed to bin and shared libraries installed to
lib, this will ensure both can find any other libraries provided by the
project. This is highly recommended as a starting point when first adding
RPATH support to projects. Note that Apple targets work a little differently
and may have a considerably different layout, so the above needs to be adapted
further to cover that platform (discussed in the next section).
需要注意的一个弱点是,虽然加载程序能够理解$ORIGIN,但链接器很可能无法理解。当某些内容链接到一个库,而该库本身又链接到另一个库时,这可能会导致问题。第一级链接不会出现问题,因为库将直接在链接器命令行上列出,但第二级库依赖项必须由链接器找到。当链接器不理解时$ORIGIN,它无法通过详细信息找到二级库RPATH。因此,除非路径也由其他选项(如 )指定-L,否则链接将失败,即使第一级库在技术上包含所需的所有信息。这是一个已知问题,并非 CMake 特有的,它是流行链接器(尤其是 GNUld链接器)的弱点。
One weakness to be aware of is that while loaders understand $ORIGIN, the
linker most likely will not. This can lead to problems when something links to
a library which itself links to another library. The first level of linking
does not present a problem, since the library will be listed directly on the
linker command line, but the second level of library dependency has to be found
by the linker. When the linker doesn’t understand $ORIGIN, it can’t find the
second level library via RPATH details. Therefore, unless the path is also
specified by some other option like -L, linking will fail, even though the
first level library technically contains all the information needed. This is a
known issue that is not specific to CMake, it is a weakness of popular linkers
(notably the GNU ld linker).
根据上面提到的各种属性和变量,CMake 可能需要RPATH在安装目标时更改目标的嵌入详细信息。有两种方法可以做到这一点。RPATH如果二进制文件是 ELF 格式,则默认情况下,CMake 使用内部工具直接在安装的二进制文件中重写。从 CMake 3.20 开始,还提供了 AIX 上 XCOFF 格式的等效功能(该功能称为LIBPATHXCOFF,但在 CMake 中为了方便起见仍称为RPATH)。CMake 通过在必要时RPATH填充构建来确保有足够的安装空间。RPATH除了构建时链接器命令行上可能出现的一些看起来奇怪的选项之外,开发人员基本上无法了解如何完成此操作的详细信息。对于其他二进制格式,CMake 在安装时重新链接二进制文件,并指定安装RPATH详细信息。从历史上看,这有时会让开发人员感到困惑,他们想知道为什么已经构建的东西需要再次链接,但最终重新链接是获得所需最终结果的实用方法。通过将变量设置为 true,也可以对 ELF 或 XCOFF 二进制文件强制重新链接行为CMAKE_NO_BUILTIN_CHRPATH,但这通常不应该使用,除非内部RPATH重写由于某种原因失败。
Depending on the various properties and variables mentioned above, CMake may be
required to change the embedded RPATH details of a target when it is being
installed.
There are two ways this can be done.
If the binary is in the ELF format, then by default, CMake uses an internal
tool to rewrite the RPATH directly in the installed binary.
From CMake 3.20, equivalent functionality for the XCOFF format on AIX is also
provided (the feature is called LIBPATH for XCOFF, but within CMake it is
still referred to as RPATH for convenience).
CMake ensures there will be enough space for the install RPATH by padding
the build RPATH if necessary.
The details of how this is done are largely hidden from the developer, other
than perhaps some odd-looking options on the linker command line at build time.
For other binary formats, CMake re-links the binary at install time, specifying
the install RPATH details instead.
Historically, this can sometimes confuse developers who wonder why something
that has already been built needs to be linked again, but ultimately the
re-linking is a pragmatic way to get the desired end result.
The re-linking behavior can be forced for ELF or XCOFF binaries too by setting
the CMAKE_NO_BUILTIN_CHRPATH variable to true, but this should not
generally be used unless the internal RPATH rewriting fails for some reason.
交叉编译时,其他一些变量可以修改RPATH二进制文件中嵌入的位置。任何RPATH以 开头的位置
CMAKE_STAGING_PREFIX都会自动将该前缀替换为CMAKE_INSTALL_PREFIX。对于构建和安装位置都是如此RPATH
。RPATH任何以 开头的
安装位置都CMAKE_SYSROOT将完全删除该前缀。
When cross compiling, a few other variables can modify the RPATH locations
embedded in binaries. Any RPATH location that starts with the
CMAKE_STAGING_PREFIX will automatically have that prefix replaced with
the CMAKE_INSTALL_PREFIX. This is true for both build and install RPATH
locations. Any install RPATH location that begins with the
CMAKE_SYSROOT will have that prefix stripped entirely.
Apple 的加载器和链接器的工作方式与其他 Unix 平台略有不同。Linux 等平台上的库仅将库名称编码到共享库(即 soname )中,而 Apple 平台则编码库的完整路径。此完整路径称为 ,install_name而 的路径部分install_name有时称为install_name_dir。链接到库的任何内容也会将完整内容编码install_name为要搜索的库。
Apple’s loader and linker work a little differently to other Unix platforms.
Whereas libraries on platforms like Linux encode just the library name into a
shared library (i.e. the soname), Apple platforms encode the full path to the
library. This full path is referred to as the install_name and the path part
of the install_name is sometimes called the install_name_dir. Anything
linking to the library also encodes the full install_name as the library to
search for.
当所有内容都安装到预期位置时,这效果很好,但对于可重定位包(包括大多数应用程序包)来说,这太不灵活了。作为处理此问题的一种方法,Apple 支持类似于 的相对基点
$ORIGIN,但占位符不同:
When everything is installed to the expected location, this works well, but
for relocatable packages (which includes most app bundles), this is
too inflexible.
As a way of dealing with this, Apple supports relative base points similar to
$ORIGIN, but the placeholders are different:
@loader_path
@loader_path
$ORIGIN,但链接器能够理解它,因此不会遇到其他链接器无法解码的问题$ORIGIN。
$ORIGIN, but the
linker is able to understand it and therefore doesn’t suffer the problems other
linkers experience with being unable to decode $ORIGIN.
@executable_path
@executable_path
@loader_path
通常是更好的选择。
@loader_path
is usually the better choice.
@rpath
@rpath
install_name_dir.
install_name_dir.
@loader_path和的组合@rpath可以提供与$ORIGIN其他平台相同的行为。CMake 提供了额外的 Apple 特定控件来帮助进行适当的设置:
The combination of @loader_path and @rpath can provide equivalent behavior
to $ORIGIN on other platforms.
CMake provides additional Apple-specific controls to help set things up
appropriately:
MACOSX_RPATH
MACOSX_RPATH
install_name_dir为。@rpath这是自 CMake 3.0 以来的默认行为,并且几乎总是可取的。它可以被覆盖INSTALL_NAME_DIR。如果CMAKE_MACOSX_RPATH在创建目标时设置了变量,则它用于初始化属性的值MACOSX_RPATH。
install_name_dir to @rpath when building for Apple platforms. This
is the default behavior since CMake 3.0 and is almost always desirable. It can
be overridden by INSTALL_NAME_DIR. If the CMAKE_MACOSX_RPATH variable is
set at the time the target is created, it is used to initialize the value of
the MACOSX_RPATH property.
INSTALL_NAME_DIR
INSTALL_NAME_DIR
install_name_dir库的install_name. 默认值
install_name通常为 形式,但对于不合适的@rpath/libsomename.dylib情况,可以指定替代形式。该属性在创建时使用变量的值进行初始化
。该属性在非 Apple 平台上被忽略。@rpathINSTALL_NAME_DIRCMAKE_INSTALL_NAME_DIR
install_name_dir part of the library’s install_name. The default
install_name usually has the form @rpath/libsomename.dylib, but for cases
where @rpath is not appropriate, INSTALL_NAME_DIR can specify an
alternative. The property is initialized with the value of the
CMAKE_INSTALL_NAME_DIR variable at the time it is created. This property
is ignored on non-Apple platforms.
对于非捆绑布局,该$ORIGIN行为也可以扩展以涵盖 Apple 案例:
For non-bundle layouts, the $ORIGIN behavior can be extended to cover the
Apple case as well:
if(APPLE)
set(base @loader_path)
else()
set(base $ORIGIN)
endif()
include(GNUInstallDirs)
file(RELATIVE_PATH relDir
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}
)
set(CMAKE_INSTALL_RPATH ${base} ${base}/${relDir})if(APPLE)
set(base @loader_path)
else()
set(base $ORIGIN)
endif()
include(GNUInstallDirs)
file(RELATIVE_PATH relDir
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}
)
set(CMAKE_INSTALL_RPATH ${base} ${base}/${relDir})
一旦使用 Apple 捆绑包或框架,布局就完全不同,并且需要替代策略来定义运行时搜索路径。例如,macOS 应用程序包在安装相关目标后或由于嵌入框架(在第 23.10 节“嵌入框架”中讨论)可能最终具有以下结构。仅显示包结构的相关部分:
Once Apple bundles or frameworks are used, the layout is completely different and alternative strategies are needed for defining the run time search paths. For example, a macOS app bundle may end up with the following structure after installing the relevant targets or as a result of embedding frameworks (discussed in Section 23.10, “Embedding Frameworks”). Only relevant parts of the bundle structure are shown:
RPATH上述安排的细节可以通过设置to
和 forINSTALL_RPATH的目标属性来实现
,并且它将被设置为。对于 iOS 应用程序,路径将是和
。安装详细信息还应该在构建时使用,以便正确处理嵌入式框架。在以下示例中,仅显示与 RPATH 相关的属性。有关需要设置的其他详细信息,请参阅第 23.6 节“代码签名”、第 23.7 节“创建和导出档案”和
第 23.10 节“嵌入框架” 。MyApp@executable_path/../FrameworksFmwk1Fmwk2@loader_path/../../..@executable_path/Frameworks@loader_path/..RPATH
RPATH details for the above arrangement could be implemented by setting the
INSTALL_RPATH target property of MyApp to @executable_path/../Frameworks
and for Fmwk1 and Fmwk2 it would be set to @loader_path/../../...
For an iOS app, the paths would be @executable_path/Frameworks and
@loader_path/.. instead.
The install RPATH details should also be used at build time so that embedded
frameworks are handled correctly.
In the following example, only the RPATH-related properties are shown.
See Section 23.6, “Code Signing”, Section 23.7, “Creating And Exporting Archives” and
Section 23.10, “Embedding Frameworks” for additional details that would need to be set.
set(CMAKE_BUILD_WITH_INSTALL_RPATH YES)
add_executable(MyApp MACOSX_BUNDLE ...)
add_library(Fmwk1 SHARED ...)
add_library(Fmwk2 SHARED ...)
# Direct linking like this assumes CMake 3.19 or later
target_link_libraries(MyApp PRIVATE Fmwk1)
target_link_libraries(Fmwk1 PRIVATE Fmwk2)
set_target_properties(MyApp PROPERTIES
INSTALL_RPATH @executable_path/../Frameworks
)
set_target_properties(Fmwk1 Fmwk2 PROPERTIES
FRAMEWORK TRUE
INSTALL_RPATH @loader_path/../../..
)set(CMAKE_BUILD_WITH_INSTALL_RPATH YES)
add_executable(MyApp MACOSX_BUNDLE ...)
add_library(Fmwk1 SHARED ...)
add_library(Fmwk2 SHARED ...)
# Direct linking like this assumes CMake 3.19 or later
target_link_libraries(MyApp PRIVATE Fmwk1)
target_link_libraries(Fmwk1 PRIVATE Fmwk2)
set_target_properties(MyApp PROPERTIES
INSTALL_RPATH @executable_path/../Frameworks
)
set_target_properties(Fmwk1 Fmwk2 PROPERTIES
FRAMEWORK TRUE
INSTALL_RPATH @loader_path/../../..
)
然后可以像这样安装应用程序包及其嵌入式框架:
The app bundle and its embedded frameworks can then be installed like so:
install(TARGETS Fmwk1 Fmwk2 MyApp
BUNDLE DESTINATION .
FRAMEWORK DESTINATION MyApp.app/Contents/Frameworks
)install(TARGETS Fmwk1 Fmwk2 MyApp
BUNDLE DESTINATION .
FRAMEWORK DESTINATION MyApp.app/Contents/Frameworks
)
框架还能够包含标头。如第 23.3 节“框架”中所述,目标可以在PUBLIC_HEADER和PRIVATE_HEADERtarget 属性中列出其公共和私有标头。然后将它们作为安装框架本身的一部分进行安装,无需进一步配置。当这些相同的目标构建在非Apple平台上时,不会有任何框架结构来保存标头(目标将被视为普通共享库),但标头仍然可以安装到指定位置:
Frameworks also have the ability to contain headers.
As outlined in Section 23.3, “Frameworks”, targets can list their public and private
headers in the PUBLIC_HEADER and PRIVATE_HEADER target properties.
These are then installed as part of installing the framework itself with no
further configuration needed.
When those same targets are built on non-Apple platforms, there won’t be any
framework structure to hold the headers (the targets would be treated as
ordinary shared libraries), but the headers can still be installed to a
nominated location:
install(TARGETS MyShared
FRAMEWORK # Apple framework case
DESTINATION ...
LIBRARY # Non-Apple case
DESTINATION ...
PUBLIC_HEADER
DESTINATION ...
PRIVATE_HEADER
DESTINATION ...
)install(TARGETS MyShared
FRAMEWORK # Apple framework case
DESTINATION ...
LIBRARY # Non-Apple case
DESTINATION ...
PUBLIC_HEADER
DESTINATION ...
PRIVATE_HEADER
DESTINATION ...
)
EXPORT安装项目目标时,它们可以使用带有 的选项
指定它们所属的导出集的名称install(TARGETS)。然后可以使用不同形式的命令安装该导出集:
When project targets are installed, they can specify the name of an export set
to which they belong using the EXPORT option with install(TARGETS).
That export set can then be installed using a different form of the command:
install(EXPORT exportName
DESTINATION dir
[FILE name.cmake]
[NAMESPACE namespace]
[PERMISSIONS permissions...]
[EXPORT_LINK_INTERFACE_LIBRARIES]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[CONFIGURATIONS configs...]
)install(EXPORT exportName
DESTINATION dir
[FILE name.cmake]
[NAMESPACE namespace]
[PERMISSIONS permissions...]
[EXPORT_LINK_INTERFACE_LIBRARIES]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[CONFIGURATIONS configs...]
)
安装导出集会在指定的目标位置创建一个dir具有指定name.cmake文件名的文件(必须以 结尾.cmake)。如果未给出该选项,则使用FILE
基于的默认文件名。exportName生成的文件将包含 CMake 命令,这些命令为导出集中的每个目标定义导入目标。该文件的目的是供其他项目包含它,以便它们可以引用该项目的目标并拥有有关接口属性和目标间关系的完整信息。在一些限制下,使用项目可以像对待其自己的常规目标一样对待导入的目标。这些导出文件通常不直接由项目包含,它们旨在由配置包使用,然后其他项目使用该命令找到该配置包(第 26.8 节“编写配置包文件”find_package()
中有更详细的介绍)
本章稍后)。
Installing an export set creates a file at the nominated destination dir with
the specified name.cmake file name (it must end in .cmake). If the FILE
option is not given, a default file name based on the exportName is used. The
generated file will contain CMake commands that define an imported target for
each target in the export set. The purpose of this file is for other projects
to include it so that they can refer to this project’s targets and have full
information about the interface properties and inter-target relationships. With
some limitations, the consuming project can then treat the imported targets
just like any of its own regular targets. These export files are not usually
included directly by projects, they are intended to be used by a config
package, which is then found by other projects using the find_package()
command (this is covered in more detail in Section 26.8, “Writing A Config Package File”
later in this chapter).
当给出该选项时,每个目标
在创建其关联的导入目标时NAMESPACE都会在其名称前面添加。namespace考虑以下示例:
When the NAMESPACE option is given, each target will have namespace
prepended to its name when creating its associated imported target. Consider
the following example:
add_library(MyShared SHARED ...)
add_library(BagOfBeans::MyShared ALIAS MyShared)
install(TARGETS MyShared
EXPORT BagOfBeans
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(EXPORT BagOfBeans
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/BagOfBeans
NAMESPACE BagOfBeans::
)add_library(MyShared SHARED ...)
add_library(BagOfBeans::MyShared ALIAS MyShared)
install(TARGETS MyShared
EXPORT BagOfBeans
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(EXPORT BagOfBeans
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/BagOfBeans
NAMESPACE BagOfBeans::
)
上面的示例遵循第 17.4 节“推荐实践”中的建议,其中每个常规目标也有一个ALIAS与之关联的命名空间。当安装非别名MyShared目标的导出时,将使用与别名目标相同的命名空间(即BagOfBeans::)。这允许使用导出的详细信息的项目以与该项目引用别名 ( ) 相同的方式引用目标BagOfBeans::MyShared。然后,使用项目可以选择直接通过添加此项目add_subdirectory()
或通过拉入导出文件,但无论选择哪种方法,find_package()仍然使用相同的
目标名称。BagOfBeans::MyShared这种重要的模式正在成为 CMake 社区对项目的相当普遍的期望,因此尝试遵循它符合大多数项目的利益。
The above example follows the advice from Section 17.4, “Recommended Practices” where
each regular target also has a namespaced ALIAS associated with it. When
installing the export for the non-alias MyShared target, the same namespace
is used as for the alias target (i.e. BagOfBeans::). This allows projects
that consume the exported details to refer to the target in the same way as
this project can refer to the alias (BagOfBeans::MyShared). Consuming
projects can then elect to add this project directly via add_subdirectory()
or pull in the export file via find_package(), yet still use the same
BagOfBeans::MyShared target name regardless of which method was chosen. This
important pattern is emerging as a fairly common expectation on projects among
the CMake community, so it is in most projects’ interests to try to follow it.
当通过(第 28.2 节“FetchContent”add_subdirectory()中深入介绍的一种强大技术
)将多个项目组合到单个构建中时,可能会出现一个问题,即不同的项目可能会定义具有相同名称的目标。CMake 要求所有全局目标都是唯一的,因此此类项目不能以这种方式组合。为了避免这种情况,项目可以为其目标指定一个项目特定的名称,例如而不只是. 无论用作导出的命名空间前缀,通常也可以用作目标名称的合适前缀(用下划线替换或完全删除它)。使用此策略,为了避免在导出的名称中重复前缀,可以将目标的 target 属性设置为不同的名称,以便仅在导出目标时使用。例如:MyProj_AlgoAlgo::EXPORT_NAME
One problem that can arise when combining multiple projects into a single
build via add_subdirectory() (a powerful technique covered in depth in
Section 28.2, “FetchContent”) is that different projects may define targets with the
same name.
CMake requires all global targets to be unique, so such projects cannot be
combined in that way.
To avoid this situation, projects can give their targets a project-specific
name, such as MyProj_Algo rather than just Algo.
Whatever is used as a namespace prefix for an export will typically also serve
as a suitable prefix for a target name (replacing the :: with an underscore
or removing it completely).
With this strategy, to avoid repeating the prefix in the exported name, the
target’s EXPORT_NAME target property can be set to a different name for
use only when exporting the target.
For example:
add_library(MyProj_Algo SHARED ...)
add_library(MyProj::Algo ALIAS MyProj_Algo)
set_target_properties(MyProj_Algo PROPERTIES
EXPORT_NAME Algo
)
install(TARGETS MyProj_Algo
EXPORT MyProj
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(EXPORT MyProj
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
NAMESPACE MyProj::
)add_library(MyProj_Algo SHARED ...)
add_library(MyProj::Algo ALIAS MyProj_Algo)
set_target_properties(MyProj_Algo PROPERTIES
EXPORT_NAME Algo
)
install(TARGETS MyProj_Algo
EXPORT MyProj
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(EXPORT MyProj
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
NAMESPACE MyProj::
)
对于上面的示例,MyProj_Algo最终将得到导出的名称
MyProj::Algo而不是MyProj::MyProj_Algo. 鼓励项目利用此功能来最大程度地减少目标名称与其他项目发生冲突的可能性,并仍然提供简洁的导出目标名称。
With the above example, MyProj_Algo will end up with the exported name
MyProj::Algo instead of MyProj::MyProj_Algo.
Projects are encouraged to make use of this feature to minimize the chances of
target name clashes with other projects and still provide concise exported
target names.
关键字后给出的导出集名称EXPORT不必与NAMESPACE. 命名空间通常与项目名称密切相关,但一系列不同的策略可能适合导出集的命名。例如,项目可以定义多个导出集,其目标共享单个命名空间,并且导出集可能对应于可以作为整体安装的逻辑单元。这些导出集可能各自对应于单个安装COMPONENT,也可能将多个组件收集在一起。下面演示了这些情况:
The name of the export set given after the EXPORT keyword does not have to
be related to the NAMESPACE. The namespace is usually closely associated with
the project name, but a range of different strategies can be appropriate for
the naming of export sets. For example, a project could define multiple export
sets with targets that share a single namespace and where the export sets might
correspond to logical units that could be installed as a whole. These export
sets might each correspond to a single install COMPONENT or they might
collect together multiple components. The following demonstrates these cases:
# Single component export
install(TARGETS algo1 EXPORT MyProj_algoFree
DESTINATION ... COMPONENT MyProj_free
)
install(EXPORT MyProj_algoFree
DESTINATION ... COMPONENT MyProj_free
)# Single component export
install(TARGETS algo1 EXPORT MyProj_algoFree
DESTINATION ... COMPONENT MyProj_free
)
install(EXPORT MyProj_algoFree
DESTINATION ... COMPONENT MyProj_free
)
# Multi component export
install(TARGETS algo2 EXPORT MyProj_algoPaid
DESTINATION ... COMPONENT MyProj_licensed_A
)
install(TARGETS algo3 EXPORT MyProj_algoPaid
DESTINATION ... COMPONENT MyProj_licensed_B
)
install(EXPORT MyProj_algoPaid
DESTINATION ... COMPONENT MyProj_licensed_dev
)# Multi component export
install(TARGETS algo2 EXPORT MyProj_algoPaid
DESTINATION ... COMPONENT MyProj_licensed_A
)
install(TARGETS algo3 EXPORT MyProj_algoPaid
DESTINATION ... COMPONENT MyProj_licensed_B
)
install(EXPORT MyProj_algoPaid
DESTINATION ... COMPONENT MyProj_licensed_dev
)
在上面的单个组件示例中,导出集仅包含algo1
目标,该目标是组件的成员MyProj_free。导出文件也是MyProj_free组件的成员,因此当安装该组件时,库和导出文件将一起安装。对于多组件示例,导出集包含algo2来自
MyProj_licensed_A组件和algo3来自MyProj_licensed_B
组件,但导出文件位于其自己的单独组件中。MyProj_licensed_dev因此,根据是否安装了组件,可以在有或没有导出文件的情况下安装目标。
In the single component example above, the export set contains just the algo1
target, which is a member of the MyProj_free component.
The export file is also a member of the MyProj_free component, so when that
component is installed, both the library and the export file will be installed
together.
For the multi component example, the export set contains algo2 from the
MyProj_licensed_A component and algo3 from the MyProj_licensed_B
component, but the export file is in its own separate component.
Therefore, the targets can be installed with or without the export file based
on whether or not the MyProj_licensed_dev component is installed.
上面的多组件导出案例强调了如何安装导出集和组件的一个重要方面。安装导出文件而不安装导出文件指向的实际目标是错误的。因此,如果用户安装了该MyProj_licensed_dev组件,则也必须安装MyProj_licensed_A和组件。MyProj_licensed_B
The multi component export case above highlights an important aspect of how
export sets and components need to be installed. It is an error to install the
export file without also installing the actual targets that the export file
points to. Thus, if the user installs the MyProj_licensed_dev component, then
the MyProj_licensed_A and MyProj_licensed_B components must also be
installed.
在该命令的其余选项中install(EXPORT),一些选项具有与install(TARGETS) 类似的效果。、PERMISSIONS和
选项适用于已安装的导出文件而不是目标本身,但在其他方面是等效的EXCLUDE_FROM_ALL。CONFIGURATIONS使用的目的地install(EXPORT)取决于项目,但遵循一些约定可能会有用。这些的动机与导出的文件用作配置包的一部分的主要方式有关,因此该主题的讨论被推迟到
下面的第 26.8 节“编写配置包文件” 。
Of the remaining options of the install(EXPORT) command, a number have
similar effects as they do for install(TARGETS). The PERMISSIONS,
EXCLUDE_FROM_ALL and CONFIGURATIONS options apply to the installed export
file rather than the targets themselves, but are otherwise equivalent.
The destination used for install(EXPORT) is up to the project, but there are
some conventions that may be useful to follow. The motivations for these are
tied to the main way the exported files are used as part of config packages, so
discussion of this topic is delayed to Section 26.8, “Writing A Config Package File”
further below.
该EXPORT_LINK_INTERFACE_LIBRARIES选项用于支持旧的 3.0 之前的 CMake 行为并与链接接口库相关。不鼓励使用它,建议项目至少更新到 3.0 作为最低 CMake 版本。
The EXPORT_LINK_INTERFACE_LIBRARIES option is for supporting old pre-3.0
CMake behavior and relates to link interface libraries. Its use is discouraged
and projects are advised to update to at least 3.0 as a minimum CMake version
instead.
该命令有一个非常相似的形式,install()专门用于导出用于 Androidndk-build项目的目标:
There is a very similar form of the install() command specifically for
exporting targets for use with Android ndk-build projects:
install(EXPORT_ANDROID_MK exportName
DESTINATION dir
[FILE name.mk]
[NAMESPACE namespace]
[PERMISSIONS permissions...]
[EXPORT_LINK_INTERFACE_LIBRARIES]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[CONFIGURATIONS configs...]
)install(EXPORT_ANDROID_MK exportName
DESTINATION dir
[FILE name.mk]
[NAMESPACE namespace]
[PERMISSIONS permissions...]
[EXPORT_LINK_INTERFACE_LIBRARIES]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[CONFIGURATIONS configs...]
)
install(EXPORT)创建一个文件供其他 CMake 项目使用
时,install(EXPORT_ANDROID_MK)创建一个可以包含的Android.mk文件ndk-build。该Android.mk文件提供了附加到导出目标的所有使用要求,因此ndk-build项目将了解链接到它们所需的所有编译器定义、标头搜索路径等。导出文件的名称可以使用选项更改FILE,但名称必须以.mk. 所有其他选项都具有与表单相同的行为
install(EXPORT)。install(EXPORT_ANDROID_MK)需要 CMake 3.7 或更高版本,但项目可能需要至少 3.11 以避免影响具有私有依赖项的静态库的错误。
Whereas install(EXPORT) creates a file for other CMake projects to consume,
install(EXPORT_ANDROID_MK) creates an Android.mk file that ndk-build can
include. The Android.mk file provides all the usage requirements attached to
the exported targets, so the ndk-build project will be aware of all the
compiler defines, header search paths and so on needed to link to them. The
name of the exported file can be changed with the FILE option, but the name
must end with .mk. All other options have the same behavior as for the
install(EXPORT) form. install(EXPORT_ANDROID_MK) requires CMake 3.7 or
later, but projects may want to require at least 3.11 to avoid a bug that
affected static libraries with private dependencies.
在某些情况下,可能需要一个导出文件而无需实际进行安装。示例场景包括针对与主构建不同的平台进行编译的子构建或由于目标名称冲突、滥用变量等而无法直接添加到主构建的第三方项目CMAKE_SOURCE_DIR。对于此类情况,CMake 提供了export()将导出文件直接写入构建树的命令:
In some situations, it may be desirable to have an export file without actually
having to do an install. Example scenarios include sub-builds that compile for
a different platform to the main build or third party projects that cannot be
added to the main build directly due to clashing target names, misuse of
variables like CMAKE_SOURCE_DIR and so on. For these sort of situations,
CMake provides the export() command which writes an export file directly
into the build tree:
export(EXPORT exportName
[NAMESPACE namespace]
[FILE fileName]
)export(EXPORT exportName
[NAMESPACE namespace]
[FILE fileName]
)
install(EXPORT)除了立即写入导出文件之外,上面的命令本质上等同于简化的命令。减少的可用选项集都具有与 相同的含义install(EXPORT),尽管fileName可以包含路径(它仍必须以 结尾.cmake)。该命令的某些其他形式export()允许导出单个目标而不是导出集,但如果已定义导出集,则上述形式可能是最容易使用和维护的。
The above is essentially equivalent to a simplified install(EXPORT) command
except the export file is written immediately. The reduced set of available
options all have the same meaning as they do for install(EXPORT), although
the fileName can include a path (it must still end in .cmake). Some other
forms of the export() command allow exporting individual targets instead of
an export set, but if export sets are already defined, the above form is likely
to be the easiest to use and maintain.
使用 CMake 3.21 或更高版本,还可以安装导入目标的运行时工件。当安装的包需要完全独立,但项目的某些目标链接到外部包提供的共享库时,这非常有用。
With CMake 3.21 or later, the runtime artifacts of imported targets can also be installed. This is useful when the installed package needs to be fully self-contained, but some of the project’s targets link to shared libraries provided by external packages.
install(IMPORTED_RUNTIME_ARTIFACTS targets...
[RUNTIME_DEPENDENCY_SET runtimeSetName]
[ [entityType]
[DESTINATION dir]
[PERMISSIONS permissions...]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
]...
)install(IMPORTED_RUNTIME_ARTIFACTS targets...
[RUNTIME_DEPENDENCY_SET runtimeSetName]
[ [entityType]
[DESTINATION dir]
[PERMISSIONS permissions...]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
]...
)
对于这种形式,entityType只能是LIBRARY、RUNTIME、
FRAMEWORK或之一BUNDLE。当为 Apple 平台构建时,将安装整个框架或捆绑包,包括标头、资源等。当为 Windows 构建时,仅安装 DLL 二进制文件,而不安装任何关联的导入库(因为后者是构建时间而不是运行时工件)。对于其他平台,仅安装共享库。
With this form, entityType can only be one of LIBRARY, RUNTIME,
FRAMEWORK or BUNDLE.
When building for Apple platforms, the entire framework or bundle will be
installed, including headers, resources, etc.
When building for Windows, only the DLL binary is installed, not any associated
import library (because the latter is a build time rather than a run time
artifact).
For other platforms, only the shared library is installed.
所有每个实体类型的关键字都具有与表单相同的含义
install(TARGETS...)。有关关键字使用的详细信息
,请参见第 26.7.1 节“运行时依赖集”RUNTIME_DEPENDENCY_SET。
All of the per-entity-type keywords have the same meaning as for the
install(TARGETS...) form.
See Section 26.7.1, “Runtime Dependency Sets” for details on the use of the
RUNTIME_DEPENDENCY_SET keyword.
在以下示例中,外部提供的ExtProj::Blah导入目标预计将是 的唯一依赖项MyApp。由于目标位置ExtProj::Blah已知,因此可以直接安装。不需要计算运行时依赖项,因此不需要运行时依赖项集。
为了简洁起见,第 26.2.2 节“RPATH”详细信息已被省略,但需要更完整的示例才能MyApp在运行时查找依赖库。
In the following example, the externally-provided ExtProj::Blah imported
target is expected to be the only dependency of MyApp.
Since the location of the ExtProj::Blah target is already known, it can be
installed directly.
No runtime dependencies need to be computed, so no runtime dependency set is
required.
Section 26.2.2, “RPATH” details have been omitted for brevity, but would be needed for a more
complete example to allow MyApp to find the dependency library at run time.
add_executable(MyApp ...)
find_package(ExtProj REQUIRED)
target_link_libraries(MyApp PRIVATE ExtProj::Blah)
install(TARGETS MyApp)
install(IMPORTED_RUNTIME_ARTIFACTS ExtProj::Blah)add_executable(MyApp ...)
find_package(ExtProj REQUIRED)
target_link_libraries(MyApp PRIVATE ExtProj::Blah)
install(TARGETS MyApp)
install(IMPORTED_RUNTIME_ARTIFACTS ExtProj::Blah)
它install(IMPORTED_RUNTIME_ARTIFACTS)有一个重要的局限性。如果要求查找未知类型库的导入目标的运行时工件,它将停止并出现错误。导入的目标通常由Find 模块创建,其中一些模块将库定义UNKNOWN为SHARED或STATIC。他们这样做是因为实际的库类型是在创建库目标之后确定的。此类导入的目标不能与install(IMPORTED_RUNTIME_ARTIFACTS).
The install(IMPORTED_RUNTIME_ARTIFACTS) has an important limitation.
It will halt with an error if asked to find the runtime artifacts of an
imported target that is a library of unknown type.
Imported targets are often created by Find modules, some
of which will define a library as UNKNOWN rather than SHARED or STATIC.
They do this because the actual library type is determined later after the
library target has been created.
Such imported targets cannot be used with install(IMPORTED_RUNTIME_ARTIFACTS).
与目标相比,安装单个文件和目录不太复杂。文件使用以下形式安装:
In contrast to targets, installing individual files and directories is less complicated. Files are installed using the following form:
install(<FILES | PROGRAMS> files...
DESTINATION dir | TYPE type
[RENAME newName]
[PERMISSIONS permissions...]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
)install(<FILES | PROGRAMS> files...
DESTINATION dir | TYPE type
[RENAME newName]
[PERMISSIONS permissions...]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
)
install(FILES)和的主要区别install(PROGRAMS)是后者如果PERMISSIONS没有给出则默认添加执行权限。这是为了安装 shell 脚本之类的东西,这些脚本需要可执行,但不是 CMake 目标。大多数选项已经很熟悉,并且与 的含义相同install(TARGETS)。RENAME仅当是单个文件时才能给出该选项files,并且它允许在安装时为该文件指定不同的名称。
The main difference between install(FILES) and install(PROGRAMS) is that
the latter adds execute permissions by default if PERMISSIONS is not given.
This is intended for installing things like shell scripts which need to be
executable, but are not CMake targets.
Most of the options are already familiar and have the same meaning as they do
for install(TARGETS).
The RENAME option can only be given if files is a single file and it allows
that file to be given a different name when installed.
CMake 3.13 及更早版本要求DESTINATION提供该选项,但从 CMake 3.14 开始,TYPE可以改为提供该选项。与本例不同install(TARGETS),必须提供两者之一,因为 CMake 无法自行推断文件类型。支持的文件类型集比目标文件类型更广泛,因为可以使用这种形式安装各种非目标文件。与每种类型关联的目标仍然以相同的方式定义,从GNUInstallDirs硬编码的默认值中获取适当的变量,如果该变量未定义,则返回到硬编码的默认值。请参阅该install()命令的 CMake 文档,了解完整的受支持类型以及关联的变量和后备值。为了获得最广泛的 CMake 版本支持,项目可能希望继续使用DESTINATION而不是,但它们应该尽可能TYPE将目标基于适当的变量。GNUInstallDirs
CMake 3.13 and earlier require the DESTINATION option to be provided, but
from CMake 3.14, the TYPE option can be given instead.
Unlike for the install(TARGETS) case, one of the two must be provided since
CMake cannot infer the file type on its own.
The set of supported file types is broader than for targets, since various
non-target files can be installed with this form.
The destination associated with each type is still defined the same way,
taking the appropriate variable from GNUInstallDirs or falling back to a
hard-coded default if that variable is undefined.
See the CMake documentation of the install() command for the full set of
supported types and the associated variables and fallback values.
For the broadest CMake version support, projects may wish to continue to
use DESTINATION rather than TYPE, but they should base the destination on
the appropriate variable from GNUInstallDirs where possible.
在某些情况下,项目可能想要安装与导入的目标关联的二进制文件,但该install(TARGETS)表单不允许直接安装导入的目标。解决此问题的一种方法是将与导入的目标关联的文件安装为普通文件。与目标相关的所有使用要求都不会被保留,但它至少允许安装二进制文件。使用此技术时,生成$<TARGET_FILE:…>器表达式和其他类似表达式特别有用。这样做的缺点是,它会将处理所有平台差异的责任重新推给项目,这对于导入的库目标来说尤其成问题。
In some situations, a project may want to install the binaries associated with
an imported target, but the install(TARGETS) form does not allow imported
targets to be installed directly. One way around this is to install the file(s)
associated with the imported target as ordinary files. All of the usage
requirements associated with the target won’t be preserved, but it does at
least allow the binaries to be installed. The $<TARGET_FILE:…> generator
expression and others like it are particularly useful when employing this
technique. A disadvantage of doing this is that it puts the onus back on the
project to handle all the platform differences, which is particularly
problematic for imported library targets.
# Assume MyImportedExe is an imported target for an
# executable not built by this project
install(PROGRAMS $<TARGET_FILE:MyImportedExe>
DESTINATION ${CMAKE_INSTALL_BINDIR}
)# Assume MyImportedExe is an imported target for an
# executable not built by this project
install(PROGRAMS $<TARGET_FILE:MyImportedExe>
DESTINATION ${CMAKE_INSTALL_BINDIR}
)
安装目录遵循与文件类似的模式,但支持的选项集有所扩展:
Installing directories follows a similar pattern to files, but the set of supported options is expanded:
install(DIRECTORY dirs...
DESTINATION dir | TYPE type
[FILE_PERMISSIONS permissions... |
USE_SOURCE_PERMISSIONS]
[DIRECTORY_PERMISSIONS permissions...]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
[MESSAGE_NEVER]
[FILES_MATCHING]
# The following block can be repeated as needed
[ [PATTERN pattern | REGEX regex]
[EXCLUDE]
[PERMISSIONS permissions...] ]
)install(DIRECTORY dirs...
DESTINATION dir | TYPE type
[FILE_PERMISSIONS permissions... |
USE_SOURCE_PERMISSIONS]
[DIRECTORY_PERMISSIONS permissions...]
[COMPONENT component]
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
[MESSAGE_NEVER]
[FILES_MATCHING]
# The following block can be repeated as needed
[ [PATTERN pattern | REGEX regex]
[EXCLUDE]
[PERMISSIONS permissions...] ]
)
如果没有任何可选参数,对于每个dirs位置,从该点开始的整个目录树都会安装到目标中
dir。如果源名称以尾部斜杠结尾,则复制源目录的内容而不是源目录本身。DESTINATION关于和参数的相同注释也TYPE适用于此表单,就像安装文件一样。
Without any of the optional arguments, for each dirs location the
entire directory tree starting at that point is installed into the destination
dir. If the source name ends with a trailing slash, then the contents of the
source directory are copied rather than the source directory itself.
The same comments regarding the DESTINATION and TYPE arguments apply
to this form as for installing files.
# Results in somewhere/foo/...
install(DIRECTORY foo DESTINATION somewhere)
# Results in somewhere/...
install(DIRECTORY foo/ DESTINATION somewhere)# Results in somewhere/foo/...
install(DIRECTORY foo DESTINATION somewhere)
# Results in somewhere/...
install(DIRECTORY foo/ DESTINATION somewhere)
、COMPONENT、EXCLUDE_FROM_ALL和选项OPTIONAL与CONFIGURATIONS其他命令具有相同的含义install()。该MESSAGE_NEVER
选项会阻止每个已安装文件的日志消息,但有人可能会争辩说,不应将其用于与所有其他已安装内容的消息保持一致。
The COMPONENT, EXCLUDE_FROM_ALL, OPTIONAL and CONFIGURATIONS options
have the same meaning as for other install() commands. The MESSAGE_NEVER
option prevents the log message for each file installed, but one could argue
that this should not be used for consistency with messages for all other
installed contents.
支持一些选项来分别控制文件和目录的权限。如果USE_SOURCE_PERMISSIONS指定,则安装的每个文件将保留与其源相同的权限。FILE_PERMISSIONS
覆盖它并使用指定的权限。如果两个选项均未给出,文件将具有与install(FILE)
使用该命令相同的默认权限。对于安装创建的目录,该
DIRECTORY_PERMISSIONS选项可用于覆盖默认值,除了还添加执行权限外,默认值与文件相同。
A few options are supported for controlling the permissions of files and
directories separately. If USE_SOURCE_PERMISSIONS is given, each file
installed will retain the same permissions as its source. FILE_PERMISSIONS
overrides that and uses the specified permissions instead. If neither option is
given, files will have the same default permissions as if the install(FILE)
command had been used. For directories created by the install, the
DIRECTORY_PERMISSIONS option can be used to override the defaults, which are
the same as for files except execute permissions are also added.
其余选项允许根据一个或多个通配符模式或正则表达式过滤一组文件。每个模式或正则表达式都会针对每个文件和目录的完整路径进行测试(始终使用正斜杠指定,即使在 Windows 上也是如此)。通配符模式必须匹配完整路径的末尾,而不仅仅是中间的某个部分,而正则表达式可以匹配路径的任何部分,因此更加灵活。如果模式或正则表达式后跟EXCLUDE关键字,则不会安装所有匹配的文件和目录。这是从目录树中排除一些特定内容的有用方法,但也可以通过在
FILES_MATCHING任何PATTERNorREGEX块之前给出关键字(一次)来实现相反的方法,这意味着仅那些与其中一个匹配的文件和目录将安装模式或正则表达式。如果既没有给出FILES_MATCHING也没有EXCLUDE给出,那么模式或正则表达式的唯一作用是用块覆盖权限PERMISSIONS。
The remaining options allow the set of files to be filtered according to one or
more wildcard patterns or regular expressions. Each pattern or regex is
tested against the full path to each file and directory (always specified with
forward slashes, even on Windows). Wildcard patterns must match the end of the
full path, not just some portion in the middle, whereas a regex can match any
part of the path and is therefore more flexible. If the pattern or regex is
followed by the EXCLUDE keyword, then all matching files and directories will
not be installed. This is a useful way of excluding just a few specific things
from the directory tree, but the reverse can also be implemented by giving the
FILES_MATCHING keyword (once) before any PATTERN or REGEX blocks, which
then means only those files and directories that do match one of the patterns
or regexes will be installed. If neither FILES_MATCHING nor EXCLUDE is
given, then the only effect of the pattern or regex is to override the
permissions with a PERMISSIONS block.
一些例子应该有助于阐明上述观点。以下示例稍稍改编自 CMake 文档,安装该
src目录及以下目录中的所有标头,并保留目录结构。
Some examples should help clarify the above points. The following example
adapted slightly from the CMake documentation installs all headers from the
src directory and below, preserving the directory structure.
install(DIRECTORY src/
DESTINATION include
FILES_MATCHING
PATTERN *.h
)install(DIRECTORY src/
DESTINATION include
FILES_MATCHING
PATTERN *.h
)
以下安装文档,跳过一些常见的隐藏文件:
The following installs documentation, skipping over some common hidden files:
install(DIRECTORY doc/ todo/ licenses
DESTINATION doc
FILES_MATCHING
REGEX \\.(DS_Store|svn) EXCLUDE
)install(DIRECTORY doc/ todo/ licenses
DESTINATION doc
FILES_MATCHING
REGEX \\.(DS_Store|svn) EXCLUDE
)
下一个示例安装示例代码和脚本,确保后者具有可执行权限:
The next example installs sample code and scripts, ensuring the latter have executable permission:
install(DIRECTORY src/
DESTINATION samples
FILES_MATCHING
REGEX "example\\.(h|c|cpp|cxx)"
PATTERN *.txt
PATTERN *.sh
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)install(DIRECTORY src/
DESTINATION samples
FILES_MATCHING
REGEX "example\\.(h|c|cpp|cxx)"
PATTERN *.txt
PATTERN *.sh
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
下面的示例省略了任何FILES_MATCHING或EXCLUDE选项,以便模式和正则表达式仅修改权限而不过滤文件和目录列表:
The example below omits any FILES_MATCHING or EXCLUDE options so that
patterns and regexes only modify permissions and not filter the list of files
and directories:
install(DIRECTORY admin_scripts
DESTINATION private
PATTERN *.sh
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
)install(DIRECTORY admin_scripts
DESTINATION private
PATTERN *.sh
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
)
在所有情况下,install(DIRECTORY)都会保留源的目录结构。如果源列表为空,则DESTINATION仍将创建为空目录。
In all cases, install(DIRECTORY) preserves the directory structure of the
source.
If the list of sources is empty, the DESTINATION will still be created as an
empty directory.
install(DIRECTORY DESTINATION somewhere/emptyDir)install(DIRECTORY DESTINATION somewhere/emptyDir)
有时,仅仅将内容复制到安装区域是不够的。任意处理可能需要作为安装的一部分来执行,例如重写文件的一部分或以编程方式生成内容。对于这些情况,CMake 支持向安装步骤添加自定义逻辑。
Sometimes simply copying things into the install area isn’t enough. Arbitrary processing may need to be performed as part of the install, such as rewriting parts of a file or generating content programmatically. For these cases, CMake supports adding custom logic to the install step.
install(SCRIPT fileName | CODE cmakeCode
[ALL_COMPONENTS | COMPONENT component]
[EXCLUDE_FROM_ALL]
)install(SCRIPT fileName | CODE cmakeCode
[ALL_COMPONENTS | COMPONENT component]
[EXCLUDE_FROM_ALL]
)
该CODE表单可用于将 CMake 命令直接嵌入为单个字符串,而该SCRIPT表单将用于include()在安装时读取脚本。和选项有其通常COMPONENT的EXCLUDE_FROM_ALL含义。CMake 3.21 添加了对该ALL_COMPONENTS选项的支持,该选项可以为基于组件的安装中的每个组件执行自定义脚本或代码。同时指定ALL_COMPONENTS和是错误的COMPONENT。
The CODE form can be used to embed CMake commands directly as a single
string, whereas the SCRIPT form will use include() to read in the script at
install time.
The COMPONENT and EXCLUDE_FROM_ALL options have their usual meanings.
CMake 3.21 added support for the ALL_COMPONENTS option, which has the effect
of executing the custom script or code for every component in a component-based
install.
It is an error to specify both ALL_COMPONENTS and COMPONENT.
对于 CMake 3.13 或更早版本,未指定在安装过程中自定义代码运行的时间点。通常,install()命令按照它们在目录范围中出现的顺序进行处理,但这不会扩展到install()嵌套在子目录中的调用。在 CMake 3.14 或更高版本中,顺序被明确定义并由策略控制CMP0082。当此策略设置为 时NEW,安装命令将按照声明的顺序执行,包括下行到子目录的说明。考虑以下示例:
With CMake 3.13 or earlier, it is unspecified at what point during installation
the custom code runs.
Generally, install() commands are processed in the order they appear in the
directory scope, but this does not extend to install() calls nested within
subdirectories.
With CMake 3.14 or later, the order is clearly defined and is controlled by
policy CMP0082.
When this policy is set to NEW, install commands are executed in the order
they are declared, including accounting for descending into subdirectories.
Consider the following example:
install(CODE [[ message("Main A") ]])
add_subdirectory(subInstall)
install(CODE [[ message("Main B") ]])install(CODE [[ message("Main A") ]])
add_subdirectory(subInstall)
install(CODE [[ message("Main B") ]])
子安装/CMakeLists.txt:
subInstall/CMakeLists.txt:
install(CODE [[ message("subdir X") ]])
install(CODE [[ message("subdir Y") ]])install(CODE [[ message("subdir X") ]])
install(CODE [[ message("subdir Y") ]])
如果策略CMP0082未设置或设置为OLD,安装期间的输出可能包含以下内容:
With policy CMP0082 unset or set to OLD, the output during install would
likely contain the following:
主A 主B 子目录X 子目录Y
Main A Main B subdir X subdir Y
将策略CMP0082设置为 时NEW,将明确定义顺序,并且安装输出将包含:
With policy CMP0082 set to NEW, the order is clearly defined and the
installation output would contain:
主A 子目录X 子目录Y 主B
Main A subdir X subdir Y Main B
可以给出多个SCRIPT和/或块,在这种情况下它们将按顺序执行。CODE其余选项最多只能指定一次。
Multiple SCRIPT and/or CODE blocks can be given, in which case they will be
executed in order.
The rest of the options can only be specified at most once.
install(CODE [[ message("Starting custom script") ]]
SCRIPT myCustomLogic.cmake
CODE [[ message("Finished custom script") ]]
COMPONENT MyProj_Runtime
)install(CODE [[ message("Starting custom script") ]]
SCRIPT myCustomLogic.cmake
CODE [[ message("Finished custom script") ]]
COMPONENT MyProj_Runtime
)
使用 CMake 3.14 或更高版本,提供的内容CODE或提供的文件名称SCRIPT可以包含生成器表达式(仅文件名/路径,而不是文件内容)。这需要将策略CMP0087设置为NEW,但与大多数其他策略不同,目录范围末尾的策略设置决定行为。CMP0087调用命令时策略的值install()不相关。这是因为 CMake 推迟处理内容,直到目录范围结束,而不是立即作为命令的一部分install()。
With CMake 3.14 or later, the contents given with CODE or the name of the
file provided for SCRIPT can contain generator expressions (only the file
name/path, not the file’s contents).
This requires policy CMP0087 to be set to NEW, but unlike most other
policies, it is the policy’s setting at the end of the directory scope that
determines the behavior.
The value of the CMP0087 policy when the install() command is called is not
relevant.
This is because CMake defers processing the contents until the end of the
directory scope, not immediately as part of the install() command.
创建包时,一个共同的愿望是使它们独立。这可以扩展到不仅包括项目自己的构建工件,还包括外部依赖项,例如编译器运行时库。CMake 提供了一些功能,可以使此任务变得更容易。
When creating packages, a common desire is to make them self-contained. This can extend to including not just the project’s own build artifacts, but also external dependencies such as compiler runtime libraries. CMake provides some features which can potentially make this task easier.
install()CMake 3.21 添加了对子命令
的直接支持,用于自动确定外部运行时依赖项并将其添加为已安装的工件。install(TARGETS)和
子install(IMPORTED_RUNTIME_ARTIFACTS)
命令都支持RUNTIME_DEPENDENCY_SET关键字。当RUNTIME_DEPENDENCY_SET给出时,两个子命令都会将它们的目标添加到命名集中。然后使用以下子命令自动安装该集中任何目标所需的所有外部提供的运行时工件:
CMake 3.21 added direct support to install() sub-commands for automatically
determining external runtime dependencies and adding them as installed
artifacts.
The install(TARGETS) and
install(IMPORTED_RUNTIME_ARTIFACTS)
sub-commands both support a RUNTIME_DEPENDENCY_SET keyword.
When RUNTIME_DEPENDENCY_SET is given, both sub-commands will add their
targets to the named set.
The following sub-command is then used to automatically install all externally
provided runtime artifacts needed by any targets in that set:
install(RUNTIME_DEPENDENCY_SET setName
[ [entityType]
[DESTINATION dir]
[PERMISSIONS permissions...]
[COMPONENT component]
[NAMELINK_COMPONENT component]
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
]...
[PRE_INCLUDE_REGEXES regexes...]
[PRE_EXCLUDE_REGEXES regexes...]
[POST_INCLUDE_REGEXES regexes...]
[POST_EXCLUDE_REGEXES regexes...]
[POST_INCLUDE_FILES files...]
[POST_EXCLUDE_FILES files...]
[DIRECTORIES directories...]
)install(RUNTIME_DEPENDENCY_SET setName
[ [entityType]
[DESTINATION dir]
[PERMISSIONS permissions...]
[COMPONENT component]
[NAMELINK_COMPONENT component]
[EXCLUDE_FROM_ALL]
[OPTIONAL]
[CONFIGURATIONS configs...]
]...
[PRE_INCLUDE_REGEXES regexes...]
[PRE_EXCLUDE_REGEXES regexes...]
[POST_INCLUDE_REGEXES regexes...]
[POST_EXCLUDE_REGEXES regexes...]
[POST_INCLUDE_FILES files...]
[POST_EXCLUDE_FILES files...]
[DIRECTORIES directories...]
)
如果entityType给定,则只能是LIBRARY、RUNTIME或FRAMEWORK,因为这些是目标可以链接到的唯一运行时工件类型。后面的详细信息entityType适用于找到的依赖项,而不是运行时依赖项集中的目标。后面的关键字entityType与 的含义和作用相同
install(TARGETS)。与往常一样,entityType可以省略,并且其后面的关键字将适用于所有依赖实体类型。
If entityType is given, it can only be LIBRARY, RUNTIME or FRAMEWORK,
since these are the only type of runtime artifacts that targets can link to.
The details following an entityType apply to the dependencies that are found,
not to the targets in the runtime dependency set.
The keywords following entityType have the same meaning and effect as for
install(TARGETS).
As usual, entityType can be omitted and the keywords that follow it will
apply to all dependency entity types.
、PRE_...和参数被转发到对 的内部调用,该POST_...调用用于计算集合中目标的依赖关系。该命令的官方文档提供了这些选项的详细解释,这里不再赘述。由于仅支持查找 Windows、Linux 和 macOS 目标平台的依赖项,因此同样的限制也适用
。此外,该命令
存在一些问题
,无法在常见的交叉编译场景中使用。DIRECTORIESfile(GET_RUNTIME_DEPENDENCIES)file(GET_RUNTIME_DEPENDENCIES)file(GET_RUNTIME_DEPENDENCIES)install(RUNTIME_DEPENDENCY_SET)file(GET_RUNTIME_DEPENDENCIES)
The PRE_..., POST_... and DIRECTORIES arguments are forwarded through
to an internal call to file(GET_RUNTIME_DEPENDENCIES), which is used to
compute the dependencies of the targets in the set.
The official documentation for the file(GET_RUNTIME_DEPENDENCIES) command
provides a detailed explanation of these options, so it is not repeated here.
Since file(GET_RUNTIME_DEPENDENCIES) only supports finding dependencies for
Windows, Linux and macOS target platforms, the same limitations apply to
install(RUNTIME_DEPENDENCY_SET) as well.
Furthermore, the file(GET_RUNTIME_DEPENDENCIES) command is
known to have problems
that prevent it from being used in common cross-compiling scenarios.
以下是一个最小示例,显示了将应用程序及其外部依赖项安装到默认安装位置的步骤:
The following is a minimal example showing the steps for installing an application and its external dependencies to the default install locations:
add_executable(MyApp ...)
install(TARGETS MyApp RUNTIME_DEPENDENCY_SET appDeps ...)
install(RUNTIME_DEPENDENCY_SET appDeps)add_executable(MyApp ...)
install(TARGETS MyApp RUNTIME_DEPENDENCY_SET appDeps ...)
install(RUNTIME_DEPENDENCY_SET appDeps)
该install(TARGETS)子命令还提供了一个RUNTIME_DEPENDENCIES
选项,可用于有效地组合两个install()调用(内部生成随机运行时依赖集名称):
The install(TARGETS) sub-command also provides a RUNTIME_DEPENDENCIES
option which can be used to effectively combine the two install() calls (a
random runtime dependency set name is generated internally):
add_executable(MyApp ...)
install(TARGETS MyApp RUNTIME_DEPENDENCIES ...)add_executable(MyApp ...)
install(TARGETS MyApp RUNTIME_DEPENDENCIES ...)
该install(RUNTIME_DEPENDENCY_SET)命令搜索运行时依赖项集中目标的所有依赖项。这意味着它还会将系统库添加到要安装的依赖项集中,这通常是不可取的。即使对于简单的“hello world”可执行文件,使用 GCC 构建典型的 Linux 桌面系统也可能会添加系统库来满足以下依赖关系:
The install(RUNTIME_DEPENDENCY_SET) command searches for all dependencies
of targets in the runtime dependency set.
This means it also adds system libraries to the set of dependencies to install,
which will often be undesirable.
Even for a simple "hello world" executable, building with GCC for a typical
Linux desktop system may add system libraries to satisfy the following
dependencies:
人们可以使用该PRE_EXCLUDE_REGEXES选项来跳过这些依赖项:
One can use the PRE_EXCLUDE_REGEXES option to skip these dependencies:
add_executable(MyApp main.cpp)
install(TARGETS MyApp
RUNTIME_DEPENDENCY_SET appDeps
)
install(RUNTIME_DEPENDENCY_SET appDeps
PRE_EXCLUDE_REGEXES
[[libc\.so\..*]]
[[libgcc_s\.so\..*]]
[[libm\.so\..*]]
[[libstdc\+\+\.so\..*]]
)add_executable(MyApp main.cpp)
install(TARGETS MyApp
RUNTIME_DEPENDENCY_SET appDeps
)
install(RUNTIME_DEPENDENCY_SET appDeps
PRE_EXCLUDE_REGEXES
[[libc\.so\..*]]
[[libgcc_s\.so\..*]]
[[libm\.so\..*]]
[[libstdc\+\+\.so\..*]]
)
Windows 上也存在类似的情况,运行时 API 集将添加到依赖项中。不应安装这些库,它们是将由操作系统提供的库的抽象。这些可以以类似的方式排除(这只是一个示例,项目将希望调整正则表达式集以满足他们的需求):
A similar situation exists on Windows, where the runtime API sets will be added to the dependencies. These should not be installed, they are abstractions for libraries that will be provided by the operating system. These can be excluded in a similar way (this is just an example, projects will want to tweak the set of regular expressions to suit their needs):
install(RUNTIME_DEPENDENCY_SET appDeps
PRE_EXCLUDE_REGEXES
[[api-ms-win-.*]]
[[ext-ms-.*]]
[[kernel32\.dll]]
POST_EXCLUDE_REGEXES
[[.*/system32/.*\.dll]]
)install(RUNTIME_DEPENDENCY_SET appDeps
PRE_EXCLUDE_REGEXES
[[api-ms-win-.*]]
[[ext-ms-.*]]
[[kernel32\.dll]]
POST_EXCLUDE_REGEXES
[[.*/system32/.*\.dll]]
)
在某些情况下,依赖项本身可能需要更多的库。该file(GET_RUNTIME_DEPENDENCIES)命令能够递归地解决这些问题,包括在适当的情况下遵循 RPATH 位置。但可能出现的一个常见问题是 RPATH 可以包含特殊字符串,例如$ORIGIN. 从 CMake 3.21.1 开始,file(GET_RUNTIME_DEPENDENCIES)不处理这些并将忽略它们,这可能导致找不到某些依赖项。DIRECTORIESLinux 上的解决方法是使用关键字 of手动指定命令要查找的其他位置install(RUNTIME_DEPENDENCY_SET)。这可能会导致警告,但至少会让操作成功。
In some cases, a dependency may itself require further libraries.
The file(GET_RUNTIME_DEPENDENCIES) command is able to resolve these
recursively, including following RPATH locations where appropriate.
A common problem that can arise though is that an RPATH can contain special
strings like $ORIGIN.
As of CMake 3.21.1, file(GET_RUNTIME_DEPENDENCIES) does not handle these and
will ignore them, which can lead to some dependencies not being found.
A workaround on Linux is to manually specify additional places for the command
to look using the DIRECTORIES keyword of install(RUNTIME_DEPENDENCY_SET).
This can lead to warnings, but will at least allow the operation to succeed.
该InstallRequiredSystemLibraries模块旨在为项目提供主要编译器的相关运行时库的详细信息。它比运行时依赖项集支持成熟得多,但它可以安装的依赖项类型也受到更多限制。
The InstallRequiredSystemLibraries module is intended to provide projects
with the details of relevant run time libraries for the major compilers.
It is much more mature than the runtime dependency set support, but also more
restricted in the type of dependencies it can install.
该模块为 Intel(所有主要平台)和 Visual Studio(仅限 Windows)提供编译器运行时。使用该模块相当简单,项目要么选择让模块代表install()其定义命令,要么可以要求填充相关变量,以便为自己创建必要的命令。install()在最简单的情况下,项目可以依赖默认值,但建议
至少设置命令的组件。
The module provides compiler runtimes for Intel (all major platforms) and
Visual Studio (Windows only).
Using the module is fairly straightforward, with projects either choosing to
let the module define the install() commands on its behalf, or it can ask for
the relevant variables to be populated so it can create the necessary commands
for itself.
In the simplest case, projects can rely on the defaults, although
setting at least the component for the install() commands is recommended.
set(CMAKE_INSTALL_SYSTEM_RUNTIME_COMPONENT MyProj_Runtime)
include(InstallRequiredSystemLibraries)set(CMAKE_INSTALL_SYSTEM_RUNTIME_COMPONENT MyProj_Runtime)
include(InstallRequiredSystemLibraries)
默认安装位置适用bin于 Windows 和lib所有其他平台。这可能与大多数项目的典型安装布局相匹配,但可以使用CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION
变量覆盖它:
The default install locations are bin for Windows and lib for all other
platforms. This is likely to match the typical install layout of most projects,
but it can be overridden with the CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION
variable:
include(GNUInstallDirs)
if(WIN32)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION
${CMAKE_INSTALL_BINDIR}
)
else()
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION
${CMAKE_INSTALL_LIBDIR}
)
endif()
set(CMAKE_INSTALL_SYSTEM_RUNTIME_COMPONENT
MyProj_Runtime
)
include(InstallRequiredSystemLibraries)include(GNUInstallDirs)
if(WIN32)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION
${CMAKE_INSTALL_BINDIR}
)
else()
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION
${CMAKE_INSTALL_LIBDIR}
)
endif()
set(CMAKE_INSTALL_SYSTEM_RUNTIME_COMPONENT
MyProj_Runtime
)
include(InstallRequiredSystemLibraries)
如果项目想要install()自己定义命令,则需要
CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP在包含模块之前设置为 true。然后,项目可以使用变量访问运行时库列表
CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS:
If a project wants to define the install() commands itself, it needs to set
CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP to true before including the
module. The project can then access the list of runtime libraries using the
CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS variable:
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries)
include(GNUInstallDirs)
if(WIN32)
install(FILES ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}
DESTINATION ${CMAKE_INSTALL_BINDIR}
)
else()
install(FILES ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
endif()set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries)
include(GNUInstallDirs)
if(WIN32)
install(FILES ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}
DESTINATION ${CMAKE_INSTALL_BINDIR}
)
else()
install(FILES ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
endif()
使用英特尔编译器时,默认install()命令不仅仅安装CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS. 他们还安装一些未通过任何记录的变量提供给项目的目录。对于那些有兴趣探索这些附加内容是否需要的开发人员,可以CMAKE_INSTALL_SYSTEM_RUNTIME_DIRECTORIES在模块的实现中搜索以了解这些附加内容是如何构造的。
When using Intel compilers, the default install() commands install more than
just the contents of CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS. They also install
some directories not provided to the project through any documented variable.
For those developers interested in exploring whether these additional contents
are desirable or not, search for CMAKE_INSTALL_SYSTEM_RUNTIME_DIRECTORIES in
the module’s implementation to see how these additional contents are
constructed.
使用 Visual Studio 编译器安装各种其他运行时组件(例如 Windows 通用 CRT、MFC 和 OpenMP 库)时,还可以使用一些进一步的控件。还可以强制安装运行时库的调试版本。这些都在模块的文档中进行了清楚的描述,因此感兴趣的读者可以参考那里以获取更多详细信息。
Some further controls are available when using Visual Studio compilers to install various other run time components, such as Windows Universal CRT, MFC and OpenMP libraries. The installation of debug versions of runtime libraries can also be enforced. These are all described clearly in the module’s documentation, so the interested reader is referred to there for further details.
另一对模块也可用于安装项目的运行时依赖项。和模块可以被认为BundleUtilities是GetPrerequisites运行时依赖集方法的先驱。这两个模块使用特定于平台的工具直接询问已安装的二进制文件,并递归地复制丢失的库。这些模块可能相当难以使用,在可以使用上述任一方法的情况下应避免使用这些模块。
Another pair of modules can also be used to install a project’s run time
dependencies.
The BundleUtilities and GetPrerequisites modules can be thought of
as forerunners to the run time dependency set approach.
These two modules directly interrogate the installed binaries using
platform-specific tools and recursively copy in missing libraries.
These modules can be considerably more difficult to use and should be avoided
where either of the approaches presented above can be used.
已安装项目使其可供其他 CMake 项目使用的首选方法是提供配置包文件。该文件是通过使用命令使用项目来找到的,如第 24.5 节“查找包”find_package()中所述。配置文件的名称必须与
或匹配。前者可能更常见一些,并且与下面进一步讨论的 CMake 提供的其他功能一致,但两者在其他方面是等效的。该文件预计将为已安装项目想要提供的所有库和可执行文件提供导入的目标。安装配置文件的目录应该是默认位置之一,如果安装的基点添加到变量中,该位置将进行搜索。这确保了配置文件很容易找到。从第 24.5 节“查找包”中,将搜索的完整位置集是:<packageName>Config.cmake<lowercasePackageName>-config.cmakefind_package()CMAKE_PREFIX_PATH
The preferred way for an installed project to make itself available for other
CMake projects to consume is to provide a config package file. This file is
found by consuming projects using the find_package() command, as introduced
back in Section 24.5, “Finding Packages”. The name of the config file must match either
<packageName>Config.cmake or <lowercasePackageName>-config.cmake.
The former is perhaps a little more common and is consistent
with other functionality provided by CMake discussed further below, but both
are otherwise equivalent. The file is expected to provide imported targets for
all the libraries and executables the installed project wants to make
available. The directory into which the config file is installed should be one
of the default locations that find_package() will search if the base point of
the install is added to the CMAKE_PREFIX_PATH variable. This ensures that
the config file will be easy to find. From Section 24.5, “Finding Packages”, the full set of
locations that will be searched is:
<前缀>/ <前缀>/(cmake|CMake)/ <前缀>/<包名>*/ <前缀>/<包名>*/(cmake|CMake)/ <前缀>/(lib/<arch>|lib*|share)/cmake/<packageName>*/ <前缀>/(lib/<架构>|lib*|共享)/<包名>*/ <前缀>/(lib/<arch>|lib*|share)/<packageName>*/(cmake|CMake)/ <前缀>/<包名>*/(lib/<arch>|lib*|share)/cmake/<包名>*/ <前缀>/<包名>*/(lib/<架构>|lib*|共享)/<包名>*/ <前缀>/<包名>*/(lib/<arch>|lib*|share)/<包名>*/(cmake|CMake)/
<prefix>/ <prefix>/(cmake|CMake)/ <prefix>/<packageName>*/ <prefix>/<packageName>*/(cmake|CMake)/ <prefix>/(lib/<arch>|lib*|share)/cmake/<packageName>*/ <prefix>/(lib/<arch>|lib*|share)/<packageName>*/ <prefix>/(lib/<arch>|lib*|share)/<packageName>*/(cmake|CMake)/ <prefix>/<packageName>*/(lib/<arch>|lib*|share)/cmake/<packageName>*/ <prefix>/<packageName>*/(lib/<arch>|lib*|share)/<packageName>*/ <prefix>/<packageName>*/(lib/<arch>|lib*|share)/<packageName>*/(cmake|CMake)/
在 Apple 平台上,还可以搜索以下子目录:
On Apple platforms, the following subdirectories may also be searched:
<前缀>/<包名称>.framework/Resources/ <前缀>/<包名称>.framework/Resources/CMake/ <前缀>/<包名称>.framework/Versions/*/Resources/ <前缀>/<包名称>.framework/Versions/*/Resources/CMake/ <前缀>/<包名称>.app/Contents/Resources/ <前缀>/<包名称>.app/Contents/Resources/CMake/
<prefix>/<packageName>.framework/Resources/ <prefix>/<packageName>.framework/Resources/CMake/ <prefix>/<packageName>.framework/Versions/*/Resources/ <prefix>/<packageName>.framework/Versions/*/Resources/CMake/ <prefix>/<packageName>.app/Contents/Resources/ <prefix>/<packageName>.app/Contents/Resources/CMake/
显然,这是一个很大的候选集,但最佳选择在某种程度上取决于项目预期的安装方式。当打包包含在 Linux 发行版中时,发行版本身可能有关于此类文件的预期位置的策略。理想情况下,项目应该提供一种将所需详细信息传递到构建中的方法,而不是强制每个发行版向项目携带自己的补丁以确保根据其策略安装配置文件。缓存变量非常适合此目的,因为项目可以指定默认值,但可以覆盖它而无需更改项目。在没有任何其他限制的情况下,两个非常简单且常用的位置是<prefix>/cmake和
<prefix>/lib/cmake/<packageName>,后者的变体对多架构部署更加友好(请参见下面的示例)。
Clearly that’s a large set of candidates, but the best choice depends somewhat
on how the project expects to be installed. When packaging for inclusion in a
Linux distribution, the distribution itself may have policies for where such
files are expected to be. Rather than forcing each distribution to
carry its own patches to the project to ensure the config file is installed
according to its policies, projects should ideally provide a way to pass the
required details into the build. A cache variable is ideal for this purpose,
since the project can specify a default, but it can be overridden without
having to change the project at all. In the absence of any other constraints,
two very simple and commonly used locations are <prefix>/cmake and
<prefix>/lib/cmake/<packageName>, with variations on the latter being a
little friendlier to multi-architecture deployments (see examples below).
对于通过命令提供Android.mk文件的
项目install(EXPORT_ANDROID_MK),CMake 对其位置没有特定约定。合理的安排是ndk-build
在包布局中使用专用目录,但这最终取决于项目。
For projects that provide an Android.mk file from an
install(EXPORT_ANDROID_MK) command, CMake has no specific convention for its
location. A reasonable arrangement would be to use a dedicated ndk-build
directory within the package layout, but it is ultimately up to the project.
对于仅使用单个导出集并且没有依赖项的简单 CMake 项目,install(EXPORT)可以使用该命令直接创建基本配置文件:
For simple CMake projects that use only a single export set and that have
no dependencies, the install(EXPORT) command can be used to create a basic
config file directly:
# Use the export file directly as the package config file.
# Not generally recommended except for trivial projects.
include(GNUInstallDirs)
install(EXPORT MyProj
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
NAMESPACE MyProj::
FILE MyProjConfig.cmake
)# Use the export file directly as the package config file.
# Not generally recommended except for trivial projects.
include(GNUInstallDirs)
install(EXPORT MyProj
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
NAMESPACE MyProj::
FILE MyProjConfig.cmake
)
请注意目标如何使用模块CMAKE_INSTALL_LIBDIR定义的缓存变量GNUInstallDirs来增加 Linux 发行版不需要进行任何更改的可能性。该GNUInstallDirs模块已经考虑了常见情况,并且通过定义缓存变量,可以根据需要轻松进行自定义。
Note how the destination uses the CMAKE_INSTALL_LIBDIR cache variable defined
by the GNUInstallDirs module to increase the likelihood that Linux
distributions won’t need to make any changes. The GNUInstallDirs module
already accounts for the common cases and by defining cache variables, it
allows easy customization if required.
在实际应用中,配置文件一般不会这样直接生成。更常见的是,准备一个单独的配置文件,通过命令引入导出的文件
include()。使用两个导出集的稍微扩展的示例演示了该技术:
In practice, the config file is not normally directly generated like this. More
often, a separate config file is prepared which brings in exported files via
include() commands. A slightly expanded example using two export sets
demonstrates the technique:
include(GNUInstallDirs)
install(EXPORT MyProj_Runtime
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
NAMESPACE MyProj::
FILE MyProj_Runtime.cmake
COMPONENT MyProj_Runtime
)
install(EXPORT MyProj_Development
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
NAMESPACE MyProj::
FILE MyProj_Development.cmake
COMPONENT MyProj_Development
)
install(FILES MyProjConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
)include(GNUInstallDirs)
install(EXPORT MyProj_Runtime
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
NAMESPACE MyProj::
FILE MyProj_Runtime.cmake
COMPONENT MyProj_Runtime
)
install(EXPORT MyProj_Development
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
NAMESPACE MyProj::
FILE MyProj_Development.cmake
COMPONENT MyProj_Development
)
install(FILES MyProjConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
)
include(${CMAKE_CURRENT_LIST_DIR}/MyProj_Runtime.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/MyProj_Development.cmake)include(${CMAKE_CURRENT_LIST_DIR}/MyProj_Runtime.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/MyProj_Development.cmake)
以上MyProjConfig.cmake还是很简单的。不需要外部提供的依赖项,并且假设运行时和开发组件始终都已安装。然后考虑一个场景,其中运行时组件依赖于其他名为 的包BagOfBeans。配置文件负责确保所需的目标可用
BagOfBeans,通常通过调用
find_package(). 为了方便起见,模块find_dependency()中的宏
可以用作处理和关键字的CMakeFindDependencyMacro包装器
。该宏还具有以下行为:如果无法找到所请求的包,则配置文件的处理将结束,就像在失败的调用之后立即进行了调用一样
。在实践中,这会产生简单、干净的依赖关系规范,并优雅地处理依赖关系失败。find_package()QUIETREQUIREDfind_dependency()return()find_dependency()
The above MyProjConfig.cmake is still very simple.
No externally provided dependencies are needed and it is assumed that the
runtime and the development components are always both installed.
Consider then a scenario where the runtime component depends on some other
package named BagOfBeans.
The config file is responsible for ensuring that the required targets from
BagOfBeans are available, which it typically does by calling
find_package().
As a convenience, the find_dependency() macro from the
CMakeFindDependencyMacro module can be used as a wrapper around
find_package() to handle the QUIET and REQUIRED keywords.
The find_dependency() macro also has the behavior that if it fails
to find the requested package, processing of the config file ends as though
a return() call was made immediately after the failed find_dependency()
call.
In practice, this results in simple, clean specification of dependencies with
graceful handling of dependency failures.
include(CMakeFindDependencyMacro)
find_dependency(BagOfBeans)
include(${CMAKE_CURRENT_LIST_DIR}/MyProj_Runtime.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/MyProj_Development.cmake)include(CMakeFindDependencyMacro)
find_dependency(BagOfBeans)
include(${CMAKE_CURRENT_LIST_DIR}/MyProj_Runtime.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/MyProj_Development.cmake)
请注意,在 CMake 3.15 之前,find_dependency()包含一项优化,如果检测到之前已找到请求的包,则会绕过调用。这种优化工作得很好,除非后来的调用需要请求一组不同的包组件。第一次find_dependency()成功时,3.15 之前的行为有效地锁定了找到的组件集。如果稍后调用find_dependency()传递一组不同的组件,它们将被忽略。由于find_dependency()调用通常是在安装供其他项目使用的文件中进行的,因此通常无法保证最终将使用哪个 CMake 版本。因此,如果依赖项支持包组件,项目应避免
直接find_dependency()调用find_package(),自行处理
QUIET和REQUIRED选项。${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY这两个选项作为变量和
传递到配置文件
${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED。始终使用${CMAKE_FIND_PACKAGE_NAME}包名称而不是硬编码包名称,因为可能存在大小写差异。
Be aware that prior to CMake 3.15, find_dependency() contained an
optimization that bypassed the call if it detected that the requested package
had previously been found.
This optimization worked fine unless later calls needed to request a different
set of package components.
The first time find_dependency() succeeds, the pre-3.15 behavior effectively
locks in the set of components found.
If later calls to find_dependency() pass a different set of components, they
would be ignored.
Because find_dependency() calls are typically made within files that are
installed for other projects to use, it usually cannot be guaranteed what
CMake version will ultimately be used.
Therefore, if the dependency supports package components, projects should avoid
find_dependency() and call find_package() directly, handling the
QUIET and REQUIRED options themselves.
These two options are passed to the config file as the variables
${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY and
${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED.
Always use ${CMAKE_FIND_PACKAGE_NAME} rather than hard-coding the package
name, because there may be upper/lowercase differences.
unset(extraArgs)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
list(APPEND extraArgs QUIET)
endif()
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
list(APPEND extraArgs REQUIRED)
endif()
find_package(BagOfBeans COMPONENTS Foo Bar ${extraArgs})unset(extraArgs)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
list(APPEND extraArgs QUIET)
endif()
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
list(APPEND extraArgs REQUIRED)
endif()
find_package(BagOfBeans COMPONENTS Foo Bar ${extraArgs})
如果项目希望支持一些自己的可选组件,则配置文件的复杂性会显着增加。支持此类功能的一组步骤可总结如下:
If the project wishes to support some of its own components being optional, the complexity of the config file increases significantly. A set of steps to support such functionality can be summarized as follows:
find_package(),然后添加满足项目依赖性所需的任何组件。
find_package() and
add any that are needed to satisfy project dependencies.
项目还需要决定如果根本没有指定组件该怎么办。这可以被视为所有组件都已被指定为可选组件,甚至被指定为必需组件。另一种策略是加载最少的基本组件集并忽略所有其他组件。最合适的策略将取决于项目组成部分的性质。所请求的组件集将在变量中可用
${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS,并且如果将某个组件指定为必需组件而不是可选组件,
${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_<comp>则该组件将为 true。
Projects also need to decide what to do if no components are specified at all.
This could be treated as though all components had been specified as optional
components or even as required components. Another strategy is to load the
minimal set of essential components and omit all others. The most appropriate
strategy will depend on the nature of the project’s components. The set of
requested components will be available in the
${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS variable and if a component was
specified as being required rather than optional,
${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_<comp> will be true for that
component.
配置文件不应使用 报告错误message(),而应将错误消息存储在名为 的变量中
${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE。这将被拾取,
find_package()并包含有关项目中引发错误的位置的详细信息。${CMAKE_FIND_PACKAGE_NAME}_FOUND还应该设置为 false 以指示失败。这允许find_package()正确实现不使用REQUIRED关键字的调用。如果包配置文件名为message(FATAL_ERROR …),则该包永远不会被视为可选。
Config files should not report errors using message(), they should instead
store the error message in a variable named
${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE. This will be picked up by
find_package() which will wrap it with details about where in the project the
error was raised. ${CMAKE_FIND_PACKAGE_NAME}_FOUND should also be set to
false to indicate failure. This allows find_package() to properly implement
a call that does not use the REQUIRED keyword. If the package config file
called message(FATAL_ERROR …), the package could never be treated as
optional.
另一种经常被忽视但强烈推荐的做法是,在首先检查是否可以满足所需组件之前,避免创建任何导入的目标。这可以防止在发生故障时为某些组件创建导入的目标,但不会为其他组件创建导入的目标。
Another often neglected but highly recommended practice is to avoid creating any imported targets before first checking whether the required components can be satisfied. This prevents imported targets from being created for some components but not others in the event of a failure.
下面的例子演示了如何结合以上几点。请注意在这种情况下使用的影响if(…IN_LIST…),这需要在开始时进行额外检查以处理 CMake 3.2 及更早版本。项目可能希望避免使用IN_LIST并使用替代来实现等效逻辑
list(FIND)。
IN_LIST示例中使用它主要是为了提高对其后果的认识,并展示如何在配置文件中安全地使用它。
The following example demonstrates how to incorporate the above points.
Note the impact of using if(…IN_LIST…) in this case, which requires
additional checking at the beginning to handle CMake versions 3.2 and older.
Projects may wish to avoid using IN_LIST and implement equivalent logic using
list(FIND) instead.
IN_LIST is used in the example mostly to raise awareness of its consequences
and to show how to use it safely in config files.
# We use if(...IN_LIST...), make sure it is available
if(CMAKE_VERSION VERSION_LESS 3.3)
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE
"MyProj requires CMake 3.3 or later"
)
set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE)
return()
endif()
cmake_minimum_required(VERSION 3.3...3.21)
# Work out the set of components to load
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
set(comps
${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS}
)
# Ensure Runtime is included if Development was given
if(Development IN_LIST comps AND
NOT Runtime IN_LIST comps)
list(APPEND comps Runtime)
endif()
else()
# No components given, look for all components
set(comps Runtime Development)
endif()
# Find external dependencies, storing comps in a safer
# variable name. In this example, BagOfBeans is required
# by the mandatory Runtime component.
set(${CMAKE_FIND_PACKAGE_NAME}_comps ${comps})
include(CMakeFindDependencyMacro)
find_dependency(BagOfBeans)
# Check all required components are available before
# trying to load any
foreach(comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_comps)
set(compFile
${CMAKE_CURRENT_LIST_DIR}/MyProj_${comp}.cmake
)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_${comp} AND
NOT EXISTS ${compFile})
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE
"MyProj missing required component: ${comp}"
)
set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE)
return()
endif()
endforeach()
foreach(comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_comps)
# All required components are known to exist.
# The OPTIONAL keyword allows the non-required
# components to be missing without error.
include(${CMAKE_CURRENT_LIST_DIR}/MyProj_${comp}.cmake
OPTIONAL
)
endforeach()# We use if(...IN_LIST...), make sure it is available
if(CMAKE_VERSION VERSION_LESS 3.3)
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE
"MyProj requires CMake 3.3 or later"
)
set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE)
return()
endif()
cmake_minimum_required(VERSION 3.3...3.21)
# Work out the set of components to load
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS)
set(comps
${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS}
)
# Ensure Runtime is included if Development was given
if(Development IN_LIST comps AND
NOT Runtime IN_LIST comps)
list(APPEND comps Runtime)
endif()
else()
# No components given, look for all components
set(comps Runtime Development)
endif()
# Find external dependencies, storing comps in a safer
# variable name. In this example, BagOfBeans is required
# by the mandatory Runtime component.
set(${CMAKE_FIND_PACKAGE_NAME}_comps ${comps})
include(CMakeFindDependencyMacro)
find_dependency(BagOfBeans)
# Check all required components are available before
# trying to load any
foreach(comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_comps)
set(compFile
${CMAKE_CURRENT_LIST_DIR}/MyProj_${comp}.cmake
)
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_${comp} AND
NOT EXISTS ${compFile})
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE
"MyProj missing required component: ${comp}"
)
set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE)
return()
endif()
endforeach()
foreach(comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_comps)
# All required components are known to exist.
# The OPTIONAL keyword allows the non-required
# components to be missing without error.
include(${CMAKE_CURRENT_LIST_DIR}/MyProj_${comp}.cmake
OPTIONAL
)
endforeach()
与配置文件密切相关的是其关联的版本文件。如果提供了版本文件,则其名称应符合两种形式之一,并且必须与配置文件位于同一目录中:
A close companion to the config file is its associated version file. If a version file is provided, it is expected to have a name conforming to one of two forms and it must be in the same directory as the config file:
<packageName>ConfigVersion.cmake
<packageName>ConfigVersion.cmake
<lowercasePackageName>-config-version.cmake
<lowercasePackageName>-config-version.cmake
版本文件名的形式通常遵循与其关联的配置文件相同的形式(即FooConfigVersion.cmake与 一起使用
FooConfig.cmake,而foo-config-version.cmake通常与 配对foo-config.cmake)。版本文件的目的是告知
find_package()包是否满足指定的版本要求。
find_package()在加载版本文件之前设置一些变量:
The form of the version file name generally follows the same form as its
associated config file (i.e. FooConfigVersion.cmake would go with
FooConfig.cmake, whereas foo-config-version.cmake would typically be paired
with foo-config.cmake). The purpose of the version file is to inform
find_package() whether the package meets the specified version requirements.
find_package() sets a number of variables before the version file is loaded:
PACKAGE_FIND_NAME
PACKAGE_FIND_NAME
PACKAGE_FIND_VERSION
PACKAGE_FIND_VERSION
PACKAGE_FIND_VERSION_MAJOR
PACKAGE_FIND_VERSION_MAJOR
PACKAGE_FIND_VERSION_MINOR
PACKAGE_FIND_VERSION_MINOR
PACKAGE_FIND_VERSION_PATCH
PACKAGE_FIND_VERSION_PATCH
PACKAGE_FIND_VERSION_TWEAK
PACKAGE_FIND_VERSION_TWEAK
PACKAGE_FIND_VERSION_COUNT
PACKAGE_FIND_VERSION_COUNT
version这些变量包含指定为的参数的版本详细信息find_package()。如果没有给出这样的参数,那么PACKAGE_FIND_VERSION
将为空,其他PACKAGE_FIND_VERSION_*变量将设置为 0。PACKAGE_FIND_VERSION_COUNT保存已指定的版本组件数量的计数,其余变量都有其明显的含义。
These variables contain the version details specified as the version argument
to find_package(). If no such argument was given, then PACKAGE_FIND_VERSION
will be empty and the other PACKAGE_FIND_VERSION_* variables will be set to
0. PACKAGE_FIND_VERSION_COUNT holds the count of how many version components
have been specified and the rest of the variables have their obvious meaning.
如果find_package()调用指定了版本范围而不仅仅是单个版本,则上述与版本相关的变量指的是版本范围的下限,并且还将设置以下变量:
If the find_package() call specified a version range rather than just a
single version, the above version-related variables refer to the lower end of
the version range and the following variables will also be set:
PACKAGE_FIND_VERSION_RANGE
PACKAGE_FIND_VERSION_RANGE
PACKAGE_FIND_VERSION_RANGE_MIN
PACKAGE_FIND_VERSION_RANGE_MIN
PACKAGE_FIND_VERSION_RANGE_MAX
PACKAGE_FIND_VERSION_RANGE_MAX
PACKAGE_FIND_VERSION_MIN
PACKAGE_FIND_VERSION_MIN
PACKAGE_FIND_VERSION_MIN_MAJOR
PACKAGE_FIND_VERSION_MIN_MAJOR
PACKAGE_FIND_VERSION_MIN_MINOR
PACKAGE_FIND_VERSION_MIN_MINOR
PACKAGE_FIND_VERSION_MIN_PATCH
PACKAGE_FIND_VERSION_MIN_PATCH
PACKAGE_FIND_VERSION_MIN_TWEAK
PACKAGE_FIND_VERSION_MIN_TWEAK
PACKAGE_FIND_VERSION_MIN_COUNT
PACKAGE_FIND_VERSION_MIN_COUNT
PACKAGE_FIND_VERSION_MAX
PACKAGE_FIND_VERSION_MAX
PACKAGE_FIND_VERSION_MAX_MAJOR
PACKAGE_FIND_VERSION_MAX_MAJOR
PACKAGE_FIND_VERSION_MAX_MINOR
PACKAGE_FIND_VERSION_MAX_MINOR
PACKAGE_FIND_VERSION_MAX_PATCH
PACKAGE_FIND_VERSION_MAX_PATCH
PACKAGE_FIND_VERSION_MAX_TWEAK
PACKAGE_FIND_VERSION_MAX_TWEAK
PACKAGE_FIND_VERSION_MAX_COUNT
PACKAGE_FIND_VERSION_MAX_COUNT
PACKAGE_FIND_VERSION_COMPLETE如果指定了版本或版本范围,CMake 3.19 或更高版本也会进行设置。这将是提供给 的确切版本规范find_package()。
CMake 3.19 or later will also set PACKAGE_FIND_VERSION_COMPLETE if a version
or version range was given.
This will be the exact version specification as provided to find_package().
版本文件需要根据包的实际版本检查请求的详细信息,然后设置以下变量:
The version file needs to check the requested details against the actual version of the package and then set the following variables:
PACKAGE_VERSION
PACKAGE_VERSION
PACKAGE_VERSION_EXACT
PACKAGE_VERSION_EXACT
PACKAGE_VERSION_COMPATIBLE
PACKAGE_VERSION_COMPATIBLE
PACKAGE_VERSION_UNSUITABLE
PACKAGE_VERSION_UNSUITABLE
该find_package()命令将使用此信息将以下变量传回其调用者,所有这些变量都类似于它传递到版本文件中的类似变量(此处返回的值将是包的实际版本,而不是版本要求传递给
find_package()命令):
The find_package() command will use this information to pass back the
following variables to its caller, all of which are analogous to the similar
ones it passed in to the version file (the returned values here will be the
actual version of the package, not the version requirements passed to the
find_package() command):
<packageName>_VERSION
<packageName>_VERSION
<packageName>_VERSION_MAJOR
<packageName>_VERSION_MAJOR
<packageName>_VERSION_MINOR
<packageName>_VERSION_MINOR
<packageName>_VERSION_PATCH
<packageName>_VERSION_PATCH
<packageName>_VERSION_TWEAK
<packageName>_VERSION_TWEAK
<packageName>_VERSION_COUNT
<packageName>_VERSION_COUNT
虽然项目可以自由地手动创建版本文件,但更简单且最可能更可靠的方法是使用
模块write_basic_package_version_file()提供的命令
CMakePackageConfigHelpers:
While projects are free to manually create a version file, a much simpler and
most likely more robust approach is to use the
write_basic_package_version_file() command provided by the
CMakePackageConfigHelpers module:
write_basic_package_version_file(outFile
[VERSION requiredVersion]
COMPATIBILITY compat
[ARCH_INDEPENDENT] # Requires CMake 3.14 or later
)write_basic_package_version_file(outFile
[VERSION requiredVersion]
COMPATIBILITY compat
[ARCH_INDEPENDENT] # Requires CMake 3.14 or later
)
如果VERSION给出了参数,则requiredVersion预计会采用通常的major.minor.patch.tweak形式,但只有主要部分是强制性的。如果VERSION未给出该选项,PROJECT_VERSION
则使用该变量(由命令设置project())。该
COMPATIBILITY选项指定如何确定兼容性的策略。该compat参数必须是以下值之一(请注意,大多数名称都有点误导):
If a VERSION argument is given, the requiredVersion is expected to be in
the usual major.minor.patch.tweak form, but only the major part is
compulsory. If the VERSION option is not given, the PROJECT_VERSION
variable is used instead (as set by the project() command). The
COMPATIBILITY option specifies a strategy for how the compatibility should be
determined. The compat argument must be one of the following values (be aware
that most of the names are a little misleading):
AnyNewerVersion
AnyNewerVersion
SameMajorVersion
SameMajorVersion
requiredVersion. 这对应于与语义版本控制相同的兼容性要求。请注意,如果使用 CMake 3.19.2 或更高版本写出包版本文件,此策略将仅支持版本范围。
requiredVersion. This corresponds to the same
compatibility requirements as semantic versioning.
Note that this strategy will only support version ranges if CMake 3.19.2 or
later is being used to write out the package version file.
SameMinorVersion
SameMinorVersion
requiredVersion. 仅 CMake 3.11 或更高版本支持此选择。如果使用 CMake 3.19.2 或更高版本写出包版本文件,则此策略也仅支持版本范围。
requiredVersion. This choice is only
supported with CMake 3.11 or later.
This strategy will also only support version ranges if CMake 3.19.2 or later
is being used to write out the package version file.
ExactVersion
ExactVersion
requiredVersion. 调整部分将被忽略。这一战略尤其具有误导性,目前正在进行讨论,以期可能会否决它,转而采用新的、更清晰的战略。它不支持版本范围。
requiredVersion. The tweak part is
ignored. This strategy is particularly misleading and discussions are in
progress to potentially deprecate it in favor of a new, clearer strategy.
It does not support version ranges.
该ARCH_INDEPENDENT选项(在 CMake 3.14 或更高版本中可用)指示该包与体系结构无关。通常,CMake 将检查包是否是为相同的体系结构构建的,通常是通过验证包的二进制文件是否是为相同的指针大小构建的,但指定该ARCH_INDEPENDENT选项会禁用检查。
The ARCH_INDEPENDENT option (available with CMake 3.14 or later) indicates
that the package is architecture-independent.
Normally, CMake will check that the package was built for the same architecture,
typically by verifying that the package’s binaries were built for the same
pointer size, but specifying the ARCH_INDEPENDENT option disables the
check.
独立于体系结构的包通常不包含二进制文件,因为这些二进制文件通常特定于特定体系结构(支持多种体系结构的通用二进制文件是一个值得注意的例外)。此类包通常包含文档、脚本、图像和各种其他非可执行文件。由仅头文件库组成的包可以利用此选项为所有体系结构创建单个包。
Architecture-independent packages usually contain no binaries, since those are normally specific to a particular architecture (universal binaries supporting multiple architectures being a notable exception). Such packages often contain documentation, scripts, images and various other non-executable files. A package consisting of a header-only library could take advantage of this option to create a single package for all architectures.
该CMakePackageConfigHelpers模块还提供了另一种有时可能有用的命令。该configure_package_config_file()命令旨在通过提供一些路径处理便利性,使项目更轻松地定义可重定位包。大多数项目通常不需要它,但是当包配置文件需要引用相对于基本安装位置而不是配置文件本身的位置的已安装文件时,它提供了一种更简单的方法来稳健地执行此操作。该命令具有以下形式:
The CMakePackageConfigHelpers module also provides one other command that may
sometimes be useful. The configure_package_config_file() command is
intended to make it easier for projects to define a relocatable package by
providing some path handling conveniences. It is not typically needed for most
projects, but when the package config file needs to refer to installed files
relative to the base install location rather than the location of the config
file itself, it provides a simpler way to do so robustly. The command has the
following form:
configure_package_config_file(inputFile outputFile
INSTALL_DESTINATION path
[INSTALL_PREFIX prefix]
[PATH_VARS var1 [var2...] ]
[NO_SET_AND_CHECK_MACRO]
[NO_CHECK_REQUIRED_COMPONENTS_MACRO]
)configure_package_config_file(inputFile outputFile
INSTALL_DESTINATION path
[INSTALL_PREFIX prefix]
[PATH_VARS var1 [var2...] ]
[NO_SET_AND_CHECK_MACRO]
[NO_CHECK_REQUIRED_COMPONENTS_MACRO]
)
该命令应该用作复制configure_file()带有
<Project>Config.cmake.in替换的文件的替代命令。它将用转换为绝对路径@PACKAGE_<somevar>@的内容替换表单的变量。<somevar>原始内容被视为相对于基本安装位置。以这种方式转换的每个变量都需要与选项一起列出PATH_VARS。为了使此功能发挥作用,@PACKAGE_INIT@在使用要替换的变量之前,输入文件必须位于顶部或顶部附近(请参阅下面的示例)。
The command should be used as a replacement for configure_file() to copy a
<Project>Config.cmake.in file with substitutions. It will replace variables
of the form @PACKAGE_<somevar>@ with the contents of <somevar> converted to
an absolute path. The original contents are treated as being relative to the
base install location. Each variable to be transformed in this way needs to be
listed with the PATH_VARS option. For this functionality to work, the input
file must have @PACKAGE_INIT@ at or near the top before any use of the
variables being replaced (see example further below).
是将安装到INSTALL_DESTINATION的目录outputFile,相对于INSTALL_PREFIX. 当INSTALL_PREFIX省略时,它默认为CMAKE_INSTALL_PREFIX,这通常是所需的值。通常
INSTALL_PREFIX只有在直接在构建树中使用而不是安装时才提供outputFile(即与命令结合使用export(EXPORT))。
The INSTALL_DESTINATION is the directory into which outputFile will be
installed, relative to the INSTALL_PREFIX. When INSTALL_PREFIX is omitted,
it defaults to CMAKE_INSTALL_PREFIX, which is usually the desired value. The
INSTALL_PREFIX would normally only be provided if the outputFile will be
used directly in a build tree rather than being installed (i.e. it is used in
conjunction with an export(EXPORT) command).
NO_SET_AND_CHECK_MACRO和选项NO_CHECK_REQUIRED_COMPONENTS_MACRO阻止@PACKAGE_INIT@定义某些辅助函数。在导入目标成为提供包目标的首选方式之前,需要使用变量。为了实现这一点,set_and_check()提供了一个宏,
configure_package_config_file()如果变量尚未定义,则它只会设置变量。提供导入目标的项目不需要此宏,并且可以添加 来NO_SET_AND_CHECK_MACRO防止它被定义。
The NO_SET_AND_CHECK_MACRO and NO_CHECK_REQUIRED_COMPONENTS_MACRO options
prevent @PACKAGE_INIT@ from defining some helper functions. Before imported
targets became the preferred way to provide package targets, variables needed
to be used. To facilitate this, a set_and_check() macro was provided by
configure_package_config_file() which would only set a variable if it was not
already defined. Projects providing imported targets should not need this macro
and can add the NO_SET_AND_CHECK_MACRO to prevent it being defined.
一个示例应该有助于阐明此命令的典型用法。包配置文件将从输入文件生成,如下所示:
An example should help clarify the typical usage of this command. The package config file would be generated from an input file such as the following:
@PACKAGE_INIT@
list(APPEND CMAKE_MODULE_PATH "@PACKAGE_cmakeModulesDir@")
# Include the project's export files, etc...@PACKAGE_INIT@
list(APPEND CMAKE_MODULE_PATH "@PACKAGE_cmakeModulesDir@")
# Include the project's export files, etc...
然后,项目会将其转换为最终的包配置文件,包括cmakeModulesDir变量的适当替换:
This would then be transformed by the project into the final package config
file, including an appropriate replacement for the cmakeModulesDir variable:
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
# This will be used to replace @PACKAGE_cmakeModulesDir@
set(cmakeModulesDir cmake)
configure_package_config_file(
MyProjConfig.cmake.in MyProjConfig.cmake
INSTALL_DESTINATION
${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
PATH_VARS
cmakeModulesDir
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/MyProjConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
COMPONENT ...
)include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
# This will be used to replace @PACKAGE_cmakeModulesDir@
set(cmakeModulesDir cmake)
configure_package_config_file(
MyProjConfig.cmake.in MyProjConfig.cmake
INSTALL_DESTINATION
${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
PATH_VARS
cmakeModulesDir
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/MyProjConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProj
COMPONENT ...
)
过去,当有关包的所有详细信息都通过变量提供时,通常会在返回之前检查配置文件末尾是否设置了所有必需的变量。为此目的定义了一个名为 的宏check_required_components(),但提供导入目标的项目应自行执行这些检查。仅当找到所有必需的组件时才应创建导入的目标。否则,失败的find_package()调用仍然会留下目标,这会干扰以后对find_package()相同包名称但具有不同参数的调用(例如,在不同位置进行搜索)。这使得check_required_components()宏在很大程度上变得多余。
In the past when all details about a package were provided through variables,
it was customary to check whether all required variables were set at the end
of the config file before returning.
A macro called check_required_components() was defined for this purpose, but
projects that provide imported targets should perform these checks themselves.
The imported targets should only be created if all required components will be
found.
Otherwise a failed find_package() call will still leave behind targets, which
would interfere with any later call to find_package() for the same package
name but with different arguments (e.g. to search in different locations).
This makes the check_required_components() macro largely redundant.
配置文件机制不仅限于 CMake 构建的项目,它也可以用于非 CMake 项目(尽管这还不是那么常见)。虽然 CMake 项目可以利用各种 CMake 功能来更轻松地创建所需的文件,但非 CMake 项目必须手动定义它们。对于此类项目,保持文件简单也很重要,因为它们可能由不太熟悉 CMake 的人维护。
The config file mechanism isn’t restricted to projects built by CMake, it can also be used for non-CMake projects too (although this is not yet all that common). While CMake projects can make use of various CMake features to more easily create the required files, non-CMake projects have to define them manually. For such projects, it is also important to keep the files simple, since they will likely be maintained by people not so familiar with CMake.
良好的第一步是最初放弃组件支持,而只是将包作为一组简单的导入目标提供。对于只需要提供库的项目,以下示例显示了一个相当基本的配置文件,应该作为一个很好的起点。它包括一些不同类型的库用于说明目的。
A good first step is to initially forgo component support and just make the package available as a simple set of imported targets. For projects that only need to provide libraries, the following example shows a fairly basic config file that should serve as a good starting point. It includes a few different types of libraries for illustration purposes.
# Compute the base point of the install by getting the
# directory of this file and moving up the required number
# of directories
set(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_DIR}")
foreach(i RANGE 1 NumSubdirLevels) ①
get_filename_component(
_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH
)
if(_IMPORT_PREFIX STREQUAL "/")
set(_IMPORT_PREFIX "")
break()
endif()
endforeach()
# Use a prefix specific to this project
set(projPrefix MyProj)
# Example of defining a static library imported target
add_library(${projPrefix}::MyStatic STATIC IMPORTED)
set_target_properties(${projPrefix}::MyStatic PROPERTIES
IMPORTED_LOCATION
"${_IMPORT_PREFIX}/lib/libMyStatic.a" ②
)
# Example of defining a shared library imported target with
# version details
add_library(${projPrefix}::MyShared SHARED IMPORTED)
set_target_properties(${projPrefix}::MyShared PROPERTIES
IMPORTED_LOCATION
"${_IMPORT_PREFIX}/lib/libMyShared.so.1.6.3" ③
IMPORTED_SONAME
"libMyShared.so.1" ④
)
# Another shared library example, this time for Windows
add_library(${projPrefix}::MyDLL SHARED IMPORTED)
set_target_properties(${projPrefix}::MyDLL PROPERTIES
IMPORTED_LOCATION
"${_IMPORT_PREFIX}/bin/MyShared.dll"
IMPORTED_IMPLIB
"${_IMPORT_PREFIX}/lib/MyShared.lib" ⑤
)# Compute the base point of the install by getting the
# directory of this file and moving up the required number
# of directories
set(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_DIR}")
foreach(i RANGE 1 NumSubdirLevels) ①
get_filename_component(
_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH
)
if(_IMPORT_PREFIX STREQUAL "/")
set(_IMPORT_PREFIX "")
break()
endif()
endforeach()
# Use a prefix specific to this project
set(projPrefix MyProj)
# Example of defining a static library imported target
add_library(${projPrefix}::MyStatic STATIC IMPORTED)
set_target_properties(${projPrefix}::MyStatic PROPERTIES
IMPORTED_LOCATION
"${_IMPORT_PREFIX}/lib/libMyStatic.a" ②
)
# Example of defining a shared library imported target with
# version details
add_library(${projPrefix}::MyShared SHARED IMPORTED)
set_target_properties(${projPrefix}::MyShared PROPERTIES
IMPORTED_LOCATION
"${_IMPORT_PREFIX}/lib/libMyShared.so.1.6.3" ③
IMPORTED_SONAME
"libMyShared.so.1" ④
)
# Another shared library example, this time for Windows
add_library(${projPrefix}::MyDLL SHARED IMPORTED)
set_target_properties(${projPrefix}::MyDLL PROPERTIES
IMPORTED_LOCATION
"${_IMPORT_PREFIX}/bin/MyShared.dll"
IMPORTED_IMPLIB
"${_IMPORT_PREFIX}/lib/MyShared.lib" ⑤
)
NumSubdirLevels是此配置文件位于基本安装点下方的多少个目录。例如,如果文件位于
lib/cmake/Foo/FooConfig.cmake,则应NumSubdirLevels为 3。NumSubdirLevels is how many directories this config file is
below the base install point. For example, if the file is at
lib/cmake/Foo/FooConfig.cmake, then NumSubdirLevels should be 3._IMPORT_PREFIX)。_IMPORT_PREFIX).IMPORTED_SONAME本质上是将嵌入到链接到该目标的二进制文件中的名称。在 Apple 平台上,这通常具有包含@rpath并可能包含一些子目录组件的表单。IMPORTED_SONAME is essentially the
name that will be embedded in binaries that link to this target. On Apple
platforms, this would typically have a form that includes @rpath and
potentially some subdirectory components.IMPORTED_IMPLIB可以省略,但这不太常见。IMPORTED_IMPLIB can be omitted, but this
would be less common.IMPORTED_…上述内容非常基本,显然需要针对每个平台定制各种属性,但非 CMake 项目可以自由使用它认为方便生成已安装配置文件内容的任何机制。为了增加健壮性,每个导入的库仅当其尚不存在时才应添加,如下所示:
The above is quite basic and obviously the various IMPORTED_… properties
would need to be tailored for each platform, but the non-CMake project is free
to use whatever mechanisms it finds convenient to produce the installed config
file’s contents. For added robustness, each imported library should only be
added if it does not already exist, as the following demonstrates:
if(NOT TARGET ${projPrefix}::MyStatic)
add_library(${projPrefix}::MyStatic STATIC IMPORTED)
set_target_properties(${projPrefix}::MyStatic
PROPERTIES
IMPORTED_LOCATION
"${_IMPORT_PREFIX}/lib/libMyStatic.a"
)
endif()if(NOT TARGET ${projPrefix}::MyStatic)
add_library(${projPrefix}::MyStatic STATIC IMPORTED)
set_target_properties(${projPrefix}::MyStatic
PROPERTIES
IMPORTED_LOCATION
"${_IMPORT_PREFIX}/lib/libMyStatic.a"
)
endif()
安装通常作为创建最终包的一部分完成,但也可以单独运行。开发人员可能希望检查安装中包含的文件集以及它们的安装位置,因此可能需要安装到临时暂存区域。
第 26.1.2 节“基本安装位置”已经讨论了如何通过构建
install目标(或INSTALL某些生成器)来做到这一点。
An install is typically done as part of creating a final package, but it can
also be run on its own.
Developers may wish to check the set of files included in an install
and where they get installed to, so installing to a temporary staging area
can be desirable.
Section 26.1.2, “Base Install Location” already discussed how to do this by building the
install target (or INSTALL for some generators).
CMake 3.15 添加了对直接调用安装而无需通过构建工具的支持。要利用此功能,--install可以将该选项作为命令行上的第一个参数给出cmake,后跟要安装的 CMake 项目的构建目录的顶部。可以给出其他命令行参数来控制安装位置、安装哪些组件、使用哪些构建配置(在多配置生成器的情况下)以及是否在执行安装之前执行剥离。指定这些附加信息的能力是使用此方法而不是构建构建目标的主要原因之一install。以下示例展示了便利性和灵活性:
CMake 3.15 added support for invoking an install directly without going through
the build tool.
To make use of this capability, the --install option can be given as the
first argument on the cmake command line, followed by the top of the build
directory for the CMake project to install.
Other command-line arguments can be given to control where to install to, which
components to install, which build configuration to use (in the case of
multi-configuration generators) and whether to perform stripping before
carrying out the install.
The ability to specify these additional pieces of information is one of the
main reasons to use this method as opposed to building the install build
target.
The following examples demonstrate the convenience and flexibility:
cmake --install /path/to/build/dir \
--prefix /path/to/staging/area \
--config 调试\
--组件MyProj_Developmentcmake --install /path/to/build/dir \
--prefix /path/to/staging/area \
--config Debug \
--component MyProj_Developmentcmake --install /path/to/build.dir \
--前缀 /mnt/rpi4/staging \
- 条cmake --install /path/to/build.dir \
--prefix /mnt/rpi4/staging \
--stripcmake -P也可以使用脚本模式通过构建目录顶部的文件来完成上述大部分操作cmake_install.cmake,但它不太方便,并且缺乏--strip.
It is also possible to do most of the above using cmake -P script mode with
the cmake_install.cmake file at the top of the build directory, but it
is less convenient and lacks a documented equivalent for --strip.
安装是一个重要的主题,需要良好的规划并了解每个预期的部署平台。项目最初只关注预期平台集的子集是很常见的,但延迟安装和部署的任何规划可能会导致必须在项目发布周期的后期处理意外的复杂性和平台差异。项目应该清楚地了解已安装的文件和目录结构,以及最终将支持的全套打包场景。这可以强烈影响项目结构,包括诸如如何在库之间划分功能以及因此需要在二进制文件中显示哪些符号等基本问题。
Installation is a non-trivial topic that requires good planning and an understanding of each intended deployment platform. It is common for a project to initially focus on only a subset of the intended set of platforms, but delaying any planning for installation and deployment can result in having to deal with unexpected complexities and platform differences late in a project’s release cycle. Projects should have a clear understanding of the installed file and directory structure, as well as the full set of packaging scenarios that will eventually be supported. This can strongly influence project structure, including such fundamental things as how functionality is split between libraries and what symbols need to be visible in the binaries as a result.
在可能的情况下,项目应该更喜欢遵循标准的包布局。该GNUInstallDirs模块极大地简化了该任务,即使对于 Windows 上的包也是如此。CMake 3.14 根据要安装的文件或目标的类型添加了对默认安装位置的支持,并且这些默认值基于 定义的变量GNUInstallDirs,因此鼓励项目使用该模块促进的包布局的势头越来越大。如果标准布局不合适,项目可能仍然需要至少考虑是否可以跨所有平台使用通用目录结构以简化应用程序开发。
Where possible, projects should prefer to follow standard package layouts.
The GNUInstallDirs module greatly simplifies that task, even for
packages on Windows.
CMake 3.14 added support for default install locations based on the
type of file or target being installed and those defaults are based on the
variables defined by GNUInstallDirs, so there is increasing momentum toward
encouraging projects to use the package layout facilitated by that module.
If a standard layout is not suitable, projects may still want to at least
consider if a common directory structure can be used across all
platforms to simplify application development.
强烈鼓励项目使其包可重新定位。除非需要将软件包安装到非常特定的位置,否则可重定位软件包具有显着的优势。它们为最终用户提供了更大的灵活性,更容易支持各种封装系统,并且在开发过程中更容易测试。
Projects are strongly encouraged to make their packages relocatable. Unless the package needs to be installed to a very specific location, relocatable packages have significant advantages. They offer much greater flexibility to end users, they more easily support a wide range of packaging systems and they are easier to test during development.
默认安装基点的选择是特定于平台的,CMake 提供的默认值并不总是理想的,但包创建通常会覆盖它们。避免在安装基本路径中包含软件包版本号,特别是对于可重定位的软件包。最好将这个决定留给执行安装的用户,因为不同的使用场景需要不同的目录结构,这些结构可能与特定于版本的路径不兼容。项目还应该倾向于遵循相关的适当标准,例如 Linux 的文件系统层次结构标准(通常也适用于除 Apple 之外的大多数其他基于 Unix 的平台)。如果在本地测试安装,请考虑使用cmake --installCMake 3.15 或更高版本提供的功能,以提高灵活性和易用性。
The selection of the default install base point is platform-specific and the
defaults provided by CMake are not always ideal, but package creation often
overrides them anyway. Avoid including a package version number in
the install base path, especially for relocatable packages. Prefer to leave
that decision up to the user doing the install since different usage scenarios
call for different directory structures which might not be compatible
with a version-specific path. Projects should also prefer to follow appropriate
standards where relevant, such as the Filesystem Hierarchy Standard for Linux
(also generally appropriate for most other Unix-based platforms except Apple).
If testing out installs locally, consider using the cmake --install feature
available with CMake 3.15 or later for its improved flexibility and ease of use.
定义目标使用要求时,使用$<BUILD_INTERFACE:…>
生成器表达式来正确表达构建要使用的标头搜索路径。对于要安装的任何库目标,最好使用命令INCLUDES DESTINATION部分
设置标头搜索路径install(TARGETS),而不是$<INSTALL_INTERFACE:…>
在目标本身上使用生成器表达式。这样可以更方便、更简洁。确保INCLUDES DESTINATION使用相对于安装基点的相对路径。
When defining target usage requirements, use the $<BUILD_INTERFACE:…>
generator expression to properly express the header search paths to be used by
the build. For any library target that will be installed, prefer to set the
header search path using the INCLUDES DESTINATION section of the
install(TARGETS) command rather than using $<INSTALL_INTERFACE:…>
generator expressions on the target itself. This can be more convenient and
more concise. Ensure that the INCLUDES DESTINATION uses a relative path that
is relative to the install base point.
add_library(Foo ...)
# Not ideal: embeds build paths in installed export files
target_include_directories(Foo
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
)
# Better: separate paths for build and install, with the
# latter added as part of the install() command rather than
# with the target
include(GNUInstallDirs)
target_include_directories(Foo
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
install(TARGETS Foo ...
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)add_library(Foo ...)
# Not ideal: embeds build paths in installed export files
target_include_directories(Foo
PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
)
# Better: separate paths for build and install, with the
# latter added as part of the install() command rather than
# with the target
include(GNUInstallDirs)
target_include_directories(Foo
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
install(TARGETS Foo ...
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
始终为每个已安装的实体分配COMPONENT并使用特定于项目的组件名称。当项目用作大型项目层次结构的一部分时,这允许父项目控制应如何处理子组件。一个值得遵循的良好模式是<ProjectName>_<ComponentName>
(例如MyProj_Runtime)。安装导出集时,请使用与命名空间相同的项目名称,并附加两个冒号(例如MyProj::)。类似的模式可用于目标名称,使用真实的目标名称(如
MyProj_Algo导出的名称)MyProj::Algo。targetEXPORT_NAME属性可用于自定义附加到命名空间的名称,以构造目标的完整导出名称。遵循这些命名约定将使安装的项目的使用更加直观,并且还可以防止与其他项目的包发生名称冲突。
Always assign a COMPONENT to each installed entity and use project-specific
component names. When the project is used as part of a large project hierarchy,
this allows a parent project to control how child components should be treated.
A good pattern to follow is <ProjectName>_<ComponentName>
(e.g. MyProj_Runtime).
When installing export sets, use the same project name as the namespace, with
two colons appended (e.g. MyProj::).
A similar pattern can be used for target names, using a real target name like
MyProj_Algo with an exported name MyProj::Algo.
The EXPORT_NAME target property can be used to customize the name appended to
the namespace to construct the full exported name for the target.
Following these naming conventions will make working with the installed project
more intuitive and it will also prevent name clashes with other projects’
packages.
如果项目提供了其他项目需要链接的库,则更愿意为运行时支持和开发定义单独的组件。这允许父层次项目重用运行时组件来仅打包共享库和执行所需的内容,并避免打包仅用于开发的实体,例如静态库、头文件等。它还可能减少包维护人员(例如 Linux 发行版)的工作,其中包通常分为运行时包和开发包。
If the project provides libraries that other projects are expected to link against, prefer to define separate components for runtime support and for development. This allows a parent hierarchical project to re-use the runtime component to package up just the shared libraries and things needed for execution and avoid packaging development-only entities like static libraries, header files and so on. It also potentially reduces the work of package maintainers (e.g. for Linux distributions) where packages are often split up into runtime and devel packages.
在包配置文件中,始终确保不会创建导入的目标,除非find_package()调用成功。这意味着在创建任何导入的目标之前,所有必需的组件都必须可用,并且所有必需的目标依赖项都应该存在。要引入依赖项,请
find_dependency()从CMakeFindDependencyMacro模块中使用,而不是find_package()从包配置文件中调用,除非依赖项支持包组件。如果调用find_package()引入依赖项,请确保QUIET和REQUIRED选项正确传递到依赖项的find_package()调用。还可以使用适当的变量来定义成功/失败,并向原始find_package()命令报告错误消息,而不是调用message(FATAL_ERROR
…)或类似的命令。
In package config files, always ensure no imported targets are created unless
the find_package() call is going to be successful. This means all required
components must be available and all required target dependencies should exist
before creating any imported targets. To bring in the dependencies, use
find_dependency() from the CMakeFindDependencyMacro module rather than
calling find_package() from within a package config file, unless the
dependency supports package components. If calling find_package() to bring in
a dependency, ensure the QUIET and REQUIRED options are passed through
correctly to the dependency’s find_package() call. Also use the appropriate
variables to define success/failure and to report an error message back to the
original find_package() command rather than calling message(FATAL_ERROR
…) or similar.
如果项目想要在其包中包含所有运行时依赖项,CMake 提供了多种方法来自动查找和安装它们。也就是说,大多数项目最好花精力找出它们的实际依赖项并直接安装它们。这将确保构建过程更加可预测和可靠。
If a project wants to include all runtime dependencies in its packages, CMake provides a number of ways to find and install them automatically. That said, most projects will be better off spending the effort to work out their actual dependencies and install them directly. This will ensure that the build process is more predictable and reliable.
如果项目确实想要使用自动依赖项查找和安装方法之一,则install(IMPORTED_RUNTIME_ARTIFACTS)
和install(RUNTIME_DEPENDENCY_SET)命令提供的功能是最先进且开发最活跃的。它也是最不成熟的,并且确实存在已知问题(有关
详细信息,请参阅第 26.4 节“安装导入的目标”和第 26.7.1 节“运行时依赖项集” )。如果项目不受这些缺陷的影响或者能够使用合适的解决方法,则该方法仍应被视为首选。
If the project does want to use one of the automatic dependency find-and-install
methods, the functionality offered by the install(IMPORTED_RUNTIME_ARTIFACTS)
and install(RUNTIME_DEPENDENCY_SET) commands is the most advanced and most
actively developed.
It is also the least mature and does have known issues (see
Section 26.4, “Installing Imported Targets” and Section 26.7.1, “Runtime Dependency Sets” for details).
If the project is not affected by those deficiencies or is able to use suitable
workarounds, this method should still be considered the preferred choice.
InstallRequiredSystemLibraries如果仅需要自动查找并安装编译器运行时依赖项,则该模块适用。该模块允许项目避免重复所有复杂的逻辑来为不同的 Visual Studio 版本、SDK、工具包选择等查找适当的文件。如果对英特尔编译器的支持很重要,请了解该模块默认安装的各种库并决定是否需要这些库。特别是使用 OpenMP 的项目很可能希望使用默认安装命令,而不是定义自己的命令,以便不必手动定义所需的库。
The InstallRequiredSystemLibraries module is suitable if only compiler
runtime dependencies need to be automatically found and installed.
This module allows the project to avoid having to duplicate all the complex
logic for finding the appropriate files for different Visual Studio versions,
SDKs, toolkit selection, etc.
If support for Intel compilers is important, understand the various libraries
that this module installs by default and decide whether or not these libraries
are all needed.
Projects using OpenMP in particular will most likely want to use the default
install commands rather than define their own so that the required libraries do
not have to be manually defined.
避免使用BundleUtilities和GetPrerequisites模块。它们很难使用,并且存在不太可能解决的已知问题。这两个模块都没有被积极开发。
Avoid the BundleUtilities and GetPrerequisites modules.
They are difficult to use and have known problems that are unlikely to be fixed.
Neither module is being actively developed.
发布包的创建是开发人员经常感到力不从心的领域。对于任何想要掌握跨多个平台创建强大、精美的包的艺术的人来说,各种包装系统、平台差异和惯例可能会带来非常陡峭的学习曲线。每个包管理系统总是使用自己独特的输入规范形式来说明每个包包含的内容、应如何安装、包组件如何相互关联、如何与操作系统集成等等。平台之间的差异,甚至同一平台的不同发行版之间的差异并不总是显而易见的,并且通常只有在遇到不可预见的行为或约束带来的问题后才能了解到(Windows 路径长度限制和 Linux 上系统库目录名称的不同约定就是很好的例子) )。
The creation of release packages is an area where developers frequently feel out of their depth. The various packaging systems, platform differences and conventions can present a very steep learning curve for anyone wanting to master the art of creating robust, well presented packages across multiple platforms. Each package management system invariably uses its own unique form of input specification for what each package contains, how it should be installed, how package components relate to each other, how to integrate with the operating system and so on. Differences between platforms and even between different distributions of the same platform are not always obvious and are frequently only learned after experiencing problems from an unforeseen behavior or constraint (Windows path length restrictions and differing conventions on Linux for system library directory names are great examples of this).
尽管存在这些差异,但所使用的包装概念仍存在很大程度的共性。虽然每个系统或平台的实现方式可能不同,但它们的许多打包功能都可以用相当通用的方式来描述。CMake 和 CPack 利用这一点并提供了一个定义良好的接口来指定这些常见方面,然后将其转换为必要的包系统输入文件和命令以生成各种格式的包。这为开发人员提供了更短的学习曲线,从而提供了跨感兴趣平台生成包的相对快速的路径。
Despite all these differences, there is a substantial degree of commonality in terms of the packaging concepts used. While each system or platform might implement things differently, much of their packaging functionality can be described in a fairly generic way. CMake and CPack take advantage of this and present a well defined interface for specifying these common aspects, which are then translated into the necessary package system input files and commands to produce packages in various formats. This provides a much shorter learning curve for developers, resulting in a relatively quick path to producing packages across the platforms of interest.
CMake 和 CPack 不仅抽象了打包的常见方面,还简化了许多打包器特定功能的使用。通过为这些功能提供更简单的接口,CMake 和 CPack 使开发人员能够以更熟悉的方式利用更高级的打包功能。在大多数情况下,这是通过设置一些相关变量或使用适当的参数调用函数来完成的,所有这些都在 CPack 模块和各种包生成器的文档中定义。
CMake and CPack not only abstract away the common aspects of packaging, they also simplify the use of many packager-specific features as well. By providing a simpler interface to these features, CMake and CPack enable developers to exploit more advanced packaging features in a more familiar way. For the most part, this is done by setting a few relevant variables or calling functions with the appropriate arguments, all of which are defined in the documentation for the CPack module and the various package generators.
CPack 打包在内部实现为一个或多个安装到暂存区域,然后用于生成最终的包。这些安装是通过调用install()命令来控制的,这在前一章中进行了深入介绍。本章现在介绍该过程的后半部分,描述指定包元数据和包本身配置的变量和命令。
CPack packaging is implemented internally as one or more installs to a staging
area which is then used to produce the final package(s). These installs are
controlled by calls to the install() command, which were covered in depth in
the preceding chapter. This chapter now presents the second half of that
process, describing the variables and commands that specify the package meta
data and configuration of the packages themselves.
设置和执行打包的处理方式与测试类似。命令行工具
cpack读取输入文件并根据该文件的内容生成适当的包。如果命令行上没有明确给出输入文件,则将在当前目录中cpack使用。CPackConfig.cmake此输入文件通常由 CMake 通过包含模块来生成CPack,就像包含CTest模块如何生成ctest. 项目可以通过CMake变量和命令自定义生成的打包输入文件的内容。
Setting up and executing packaging is handled in a similar way to testing. The
cpack command line tool reads an input file and produces the appropriate
package(s) based on that file’s contents. If no input file is explicitly given
on the command line, cpack will use CPackConfig.cmake in the current
directory. This input file is most commonly produced by CMake through the
inclusion of the CPack module, just like how including the CTest module
generates the input file for ctest. Projects can customize the content of the
generated packaging input file through CMake variables and commands.
该CPack模块根据目标平台启用一些默认的包格式。cpack可以使用选项在命令行上覆盖要创建的包格式集
-G。如果应构建多种格式,则可以将它们作为分号分隔的列表提供,如下所示:
The CPack module enables a few default package formats based on the target
platform. The set of package formats to be created can be overridden on the
cpack command line with the -G option. If multiple formats should be built,
they can be provided as a semicolon-separated list like so:
cpack -G“ZIP;RPM”
cpack -G "ZIP;RPM"
如果 CMake 项目配置为使用多配置生成器(如 Xcode、Visual Studio 或 Ninja Multi-Config),则cpack需要知道应打包哪个配置的可执行文件。这是通过提供一个-C选项来完成的cpack(该-C选项被单个配置 CMake 生成器忽略):
If the CMake project was configured to use a multi configuration generator like
Xcode, Visual Studio or Ninja Multi-Config, cpack needs to know which
configuration’s executables it should package up.
This is done by giving a -C option to cpack (the -C option is ignored by
single configuration CMake generators):
cpack -C 发布
cpack -C Release
该cpack命令支持一些其他选项,但-G和-C是其中两个更常用的选项。大多数其他详细信息通常通过输入文件提供。部分原因是cpack开发人员可以构建package构建目标,而不是直接调用,该构建目标将首先构建默认all目标,然后cpack使用最少的选项进行调用。因此,项目可以更方便地确保cpack输入文件定义所有必需的设置。package
如果构建树的顶部包含名为 的文件,CMake 将自动创建目标CPackConfig.cmake。
The cpack command supports a few other options, but -G and -C are two of
the more commonly used. Most other details are typically provided through the
input file. This is in part because instead of invoking cpack directly,
developers can build the package build target which will first build the
default all target and then invoke cpack with minimal options. It is
therefore more convenient for the project to ensure the cpack input file
defines all required settings. CMake will automatically create the package
target if the top of the build tree contains a file called CPackConfig.cmake.
创建cpack输入文件的最简单方法是包含CPack
模块,这对于整个 CMake 项目只应执行一次。CPack在包含模块的地方,CPackConfig.cmake文件被写入构建树的顶部(即CMAKE_BINARY_DIR)。include(CPack)最好在顶级文件末尾处或附近执行调用CMakeLists.txt,可以直接执行,也可以通过子目录的
CMakeLists.txt. 使包含以项目是否有父项目为条件还可以确保项目仅在是顶级项目时才尝试定义打包。
The easiest way to create the cpack input file is by including the CPack
module, which should only be done once for the entire CMake project.
At the point where the CPack module is included, the CPackConfig.cmake file
is written to the top of the build tree (i.e. CMAKE_BINARY_DIR).
The call to include(CPack) is best performed at or near the end of the top
level CMakeLists.txt file, either directly or through a subdirectory’s
CMakeLists.txt.
Making the inclusion conditional on whether the project has a parent also
ensures the project only tries to define packaging if it is the top level
project.
cmake_minimum_required(VERSION 3.0)
project(MyProj)
# ...Define targets, add subdirectories, etc...
# End of the CMakeLists.txt file
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
# include(CPack) will happen inside the following call
add_subdirectory(packaging)
endif()cmake_minimum_required(VERSION 3.0)
project(MyProj)
# ...Define targets, add subdirectories, etc...
# End of the CMakeLists.txt file
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
# include(CPack) will happen inside the following call
add_subdirectory(packaging)
endif()
虽然为打包配置的大多数方面提供了默认值,但这些默认值并不总是合适的。大多数项目都希望在包含CPack模块之前设置一些基本细节,以提供更好的替代方案。特别是,建议在调用之前显式设置以下变量include(CPack):
While defaults are provided for most aspects of the packaging configuration,
these defaults are not always appropriate. Most projects will want to set some
basic details before including the CPack module to provide better
alternatives. In particular, it is recommended that the following variables be
explicitly set before calling include(CPack):
CPACK_PACKAGE_NAME
CPACK_PACKAGE_NAME
CMAKE_PROJECT_NAME则将其用作默认值。
CMAKE_PROJECT_NAME is used as a
default.
CPACK_PACKAGE_DESCRIPTION_SUMMARY
CPACK_PACKAGE_DESCRIPTION_SUMMARY
CMAKE_PROJECT_DESCRIPTION,而对于早期的 CMake 版本,默认值是空字符串。
CMAKE_PROJECT_DESCRIPTION,
whereas for earlier CMake versions the default is an empty string.
CPACK_PACKAGE_VENDOR
CPACK_PACKAGE_VENDOR
Humanity通常不适合用作占位符以外的任何用途。更喜欢使用真实的公司或组织名称而不是域名。
Humanity is not
generally suitable for anything other than acting as a placeholder until it is
set properly. Prefer to use a real company or organization name rather than a
domain name.
CPACK_PACKAGE_VERSION_MAJOR, CPACK_PACKAGE_VERSION_MINOR,CPACK_PACKAGE_VERSION_PATCH
CPACK_PACKAGE_VERSION_MAJOR, CPACK_PACKAGE_VERSION_MINOR, CPACK_PACKAGE_VERSION_PATCH
project()
命令的版本详细信息:
set(CPACK_PACKAGE_VERSION_MAJOR
${PROJECT_VERSION_MAJOR}
)
set(CPACK_PACKAGE_VERSION_MINOR
${PROJECT_VERSION_MINOR}
)
set(CPACK_PACKAGE_VERSION_PATCH
${PROJECT_VERSION_PATCH}
)
cpackCPACK_PACKAGE_VERSION将根据这三个变量自动填充,但这仅在cpack运行时发生,因此
CPACK_PACKAGE_VERSION在 CMake 处理期间尚未填充。从 CMake 3.12 开始,这些变量的默认值取自
CMAKE_PROJECT_VERSION_MAJOR,CMAKE_PROJECT_VERSION_MINOR和
CMAKE_PROJECT_VERSION_PATCH变量,这些变量仅在 CMake 3.12 中添加。这些变量是由顶级文件中的命令
VERSION详细信息设置的,因此它们比 3.12 之前相当任意的值 0、1 和 1 更有可能提供合理的默认值。也就是说,依靠这些变量来提供defaults 假定该项目始终是顶级项目,但情况可能并非总是如此。因此,始终将它们明确设置为项目真正想要的内容会更安全。project()CMakeLists.txt
project()
command:
set(CPACK_PACKAGE_VERSION_MAJOR
${PROJECT_VERSION_MAJOR}
)
set(CPACK_PACKAGE_VERSION_MINOR
${PROJECT_VERSION_MINOR}
)
set(CPACK_PACKAGE_VERSION_PATCH
${PROJECT_VERSION_PATCH}
)
cpack will automatically populate CPACK_PACKAGE_VERSION based on these
three variables, but this only occurs when cpack runs, so
CPACK_PACKAGE_VERSION won’t yet be populated during CMake processing. From
CMake 3.12, the default values for these variables are taken from the
CMAKE_PROJECT_VERSION_MAJOR, CMAKE_PROJECT_VERSION_MINOR and
CMAKE_PROJECT_VERSION_PATCH variables instead, which were only added in
CMake 3.12. These variables are set by
the VERSION details of the project() command in the top level
CMakeLists.txt file, so they are much more likely to provide sensible
defaults than the fairly arbitrary pre-3.12 values of 0, 1 and 1. That said,
relying on these variables to provide defaults assumes that the project is
always the top level project, which might not always be the case. Therefore, it
is safer to always explicitly set them to what the project really wants.
CPACK_PACKAGE_INSTALL_DIRECTORY
CPACK_PACKAGE_INSTALL_DIRECTORY
CPACK_PACKAGE_NAME。
CPACK_PACKAGE_NAME.
CPACK_VERBATIM_VARIABLES
CPACK_VERBATIM_VARIABLES
cpack配置文件的所有内容都被正确转义。默认值为 false 只是为了保持与早期 CMake 版本的向后兼容性,但旧的行为可能会导致配置文件格式错误,因此不应使用。
cpack configuration file are
properly escaped. The default value is false only to preserve backward
compatibility with earlier CMake versions, but the old behavior can lead to an
ill-formed configuration file and should not be used.
通常会设置更多变量来改善最终用户体验,特别是对于 UI 安装程序:
More variables will often be set to improve the end user experience, especially for UI installers:
CPACK_PACKAGE_DESCRIPTION_FILE
CPACK_PACKAGE_DESCRIPTION_FILE
CPACK_PACKAGE_DESCRIPTION。虽然 CMake 3.11 或更早版本没有对此进行记录,但甚至从早期版本的 CMake 就已经支持它。
CPACK_PACKAGE_DESCRIPTION. While this was not documented for CMake 3.11
or earlier, it has been supported even from early versions of CMake.
CPACK_RESOURCE_FILE_WELCOME
CPACK_RESOURCE_FILE_WELCOME
CPACK_RESOURCE_FILE_LICENSE
CPACK_RESOURCE_FILE_LICENSE
CPACK_RESOURCE_FILE_README
CPACK_RESOURCE_FILE_README
CPACK_PACKAGE_ICON
CPACK_PACKAGE_ICON
遵循上述准则的示例可能如下所示:
An example that follows the above guidelines may look something like this:
set(CPACK_PACKAGE_NAME MyProj)
set(CPACK_PACKAGE_VENDOR MyCompany)
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Example project")
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME})
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_VERBATIM_VARIABLES YES)
set(CPACK_PACKAGE_DESCRIPTION_FILE
${CMAKE_CURRENT_LIST_DIR}/Description.txt
)
set(CPACK_RESOURCE_FILE_WELCOME
${CMAKE_CURRENT_LIST_DIR}/Welcome.txt
)
set(CPACK_RESOURCE_FILE_LICENSE
${CMAKE_CURRENT_LIST_DIR}/License.txt
)
set(CPACK_RESOURCE_FILE_README
${CMAKE_CURRENT_LIST_DIR}/Readme.txt
)
include(CPack)set(CPACK_PACKAGE_NAME MyProj)
set(CPACK_PACKAGE_VENDOR MyCompany)
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Example project")
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME})
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_VERBATIM_VARIABLES YES)
set(CPACK_PACKAGE_DESCRIPTION_FILE
${CMAKE_CURRENT_LIST_DIR}/Description.txt
)
set(CPACK_RESOURCE_FILE_WELCOME
${CMAKE_CURRENT_LIST_DIR}/Welcome.txt
)
set(CPACK_RESOURCE_FILE_LICENSE
${CMAKE_CURRENT_LIST_DIR}/License.txt
)
set(CPACK_RESOURCE_FILE_README
${CMAKE_CURRENT_LIST_DIR}/Readme.txt
)
include(CPack)
为了便于cpack无参数运行和构建目标的使用package
,CPACK_GENERATOR应将变量设置为所需的包格式。如果未设置,将使用相当保守的默认生成器集。由于并非所有格式都在所有平台上受支持或适用,因此设置此变量需要逻辑来仅指定那些有意义的格式。以下示例为目标平台选择一种通用存档格式和一种本机包格式(如果已识别):
To facilitate running cpack with no arguments and the use of the package
build target, the CPACK_GENERATOR variable should be set to the desired
package formats. If not set, a fairly conservative default set of generators
will be used. Since not all formats are supported or appropriate on all
platforms, setting this variable requires logic to specify only those formats
that make sense. The following example selects one generic archive format and
one native package format for the target platform (if identified):
if(WIN32)
set(CPACK_GENERATOR ZIP WIX)
elseif(APPLE)
set(CPACK_GENERATOR TGZ productbuild)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(CPACK_GENERATOR TGZ RPM)
else()
set(CPACK_GENERATOR TGZ)
endif()if(WIN32)
set(CPACK_GENERATOR ZIP WIX)
elseif(APPLE)
set(CPACK_GENERATOR TGZ productbuild)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(CPACK_GENERATOR TGZ RPM)
else()
set(CPACK_GENERATOR TGZ)
endif()
该CPack模块还定义了生成源包所需的详细信息。它创建一个CPackSourceConfig.cmake可以用来代替的文件CPackConfig.cmake,并且当项目配置为使用 Makefile 或 Ninja 生成器时,package_source还会定义构建目标。生成源包相对简单,使用以下两个命令之一即可实现相同的效果。
The CPack module also defines the necessary details that allow a source
package to be produced. It creates a CPackSourceConfig.cmake file which can
be used instead of CPackConfig.cmake and when the project is configured to
use a Makefile or Ninja generator, a package_source build target is defined
as well. Producing the source package is relatively straightforward, with
either of the following two commands achieving the same thing.
# 所有构建生成器 cpack -G TGZ --config CPackSourceConfig.cmake # 仅 Makefile 和 Ninja 构建生成器 cmake --build 。--目标包_源
# All build generators cpack -G TGZ --config CPackSourceConfig.cmake # Makefile and Ninja build generators only cmake --build . --target package_source
源码包包含整个源码目录树。该
CPACK_SOURCE_IGNORE_FILES变量可用于过滤掉源树的某些部分,保存每个完整文件路径将与之进行比较的正则表达式列表。所有匹配的文件将从源包中省略。该变量的默认值会忽略
.git、等存储库目录.svn以及一些常见的临时文件。如果项目覆盖CPACK_SOURCE_IGNORE_FILES,则需要确保它也指定任何此类相关模式。为了避免正则表达式中的转义和引用问题,强烈建议设置
CPACK_VERBATIM_VARIABLES为 true。
The source package contains the entire source directory tree. The
CPACK_SOURCE_IGNORE_FILES variable can be used to filter out parts of the
source tree, holding a list of regular expressions that each full file path
will be compared against. All matching files will be omitted from the source
package. The default value of this variable ignores repository directories like
.git, .svn, etc. as well as some common temporary files. If a project
overrides CPACK_SOURCE_IGNORE_FILES, it will need to ensure it also specifies
any such relevant patterns. To avoid problems with escaping and quoting in the
regular expressions, it is strongly recommended to set
CPACK_VERBATIM_VARIABLES to true.
set(CPACK_VERBATIM_VARIABLES YES)
set(CPACK_SOURCE_IGNORE_FILES
/\\.git/
\\.swp
\\.orig
/CMakeLists\\.txt\\.user
/privateDir/
)set(CPACK_VERBATIM_VARIABLES YES)
set(CPACK_SOURCE_IGNORE_FILES
/\\.git/
\\.swp
\\.orig
/CMakeLists\\.txt\\.user
/privateDir/
)
如果项目在其任何install()命令中未定义任何组件,则所有包生成器将生成一个包含所有已安装内容的整体包。当项目确实定义了组件时,它为如何打包提供了更大的灵活性。还可以指定组件之间的关系,从而允许定义分层组件结构并在安装时强制执行它们之间的依赖关系。每个包生成器都以不同的方式使用这些组件详细信息,其中一些为不同的组件创建单独的包,而另一些则在单个 UI 安装程序中提供用户可选择的组件。一些安装程序甚至支持在安装时按需下载各个组件。
If a project defines no components in any of its install() commands, then all
package generators will produce a single monolithic package that contains all
installed contents. When a project does define components, it provides more
flexibility for how it can be packaged. Relationships can also be specified
between components, allowing hierarchical component structures to be defined
and dependencies between them to be enforced at install time. Each package
generator makes use of these component details in different ways, with some
creating separate packages for different components, while others present
user selectable components in a single UI installer. Some installers even
support downloading individual components on demand at install time.
上一章演示了如何将组件定义为
install()命令的一部分。这些命令仅将内容分配给组件,它们不定义任何其他组件详细信息。组件之间的关系是使用CPackComponent模块中的命令指定的,该命令会作为包含CPack模块的一部分自动包含在内。这些命令还为某些安装程序在安装过程中用来向用户提供信息的组件提供附加元数据。
The previous chapter demonstrated how to define components as part of
install() commands. Those commands only assign content to components, they do
not define any other component details. The relationships between components
are specified using commands from the CPackComponent module, which is
automatically included as part of including the CPack module. These commands
also provide additional metadata for components which some installers use to
present information to the user during installation.
模块中最重要的命令CPackComponent是
cpack_add_component(),它描述单个组件:
The most important command from the CPackComponent module is
cpack_add_component(), which describes a single component:
cpack_add_component(componentName
[DISPLAY_NAME name]
[DESCRIPTION description]
[DEPENDS comp1 [comp2...] ]
[GROUP group]
[REQUIRED | DISABLED]
[HIDDEN]
[INSTALL_TYPES type1 [type2...] ]
[DOWNLOADED]
[ARCHIVE_FILE archiveFileName]
[PLIST plistFileName]
)cpack_add_component(componentName
[DISPLAY_NAME name]
[DESCRIPTION description]
[DEPENDS comp1 [comp2...] ]
[GROUP group]
[REQUIRED | DISABLED]
[HIDDEN]
[INSTALL_TYPES type1 [type2...] ]
[DOWNLOADED]
[ARCHIVE_FILE archiveFileName]
[PLIST plistFileName]
)
虽然所有关键字都是可选的,但至少应该提供DISPLAY_NAME和DESCRIPTION,以便在安装过程中向用户呈现有意义的详细信息,并且非 UI 安装程序有足够的元数据让用户了解包的用途。如果仅在安装了一个或多个其他组件后才应安装该组件,则这些组件应与该DEPENDS选项一起列出。请注意,并非所有包类型都完全强制执行这些依赖性。可以使用选项将组件放置在特定组下
GROUP,可以使用
cpack_add_component_group()命令进一步描述该选项(下面进一步讨论)。
While all keywords are optional, the DISPLAY_NAME and DESCRIPTION should at
least be provided so that meaningful details are presented to the user during
installation and so that non-UI installers have enough metadata for users to
understand what a package is for. If the component should only be installed if
one or more other components are installed, those components should be listed
with the DEPENDS option. Note that not all package types fully enforce these
dependencies. A component can be placed under a particular group with the
GROUP option, which can be further described using the
cpack_add_component_group() command (discussed further below).
如果应始终安装某个组件,REQUIRED则应给出关键字。然后,用户将无法通过安装程序的 UI 禁用该组件。如果没有此关键字,用户可以启用或禁用该组件,默认初始状态为启用。要将此默认值更改为禁用,请添加DISABLED关键字。无论某个组件是否是必需的,都可以通过添加关键字从安装程序 UI 中隐藏它
HIDDEN。非必需但隐藏的组件通常也会被禁用,然后安装程序将仅在另一个启用的组件依赖于该组件时安装该组件。
If a component should always be installed, the REQUIRED keyword should be
given. The user will then not be able to disable that component through an
installer’s UI. Without this keyword, the component can be enabled or disabled
by the user, with the default initial state being enabled. To change this
default to disabled, add the DISABLED keyword. Whether a component is
required or not, it can also be hidden from installer UIs by adding the
HIDDEN keyword. A non-required but hidden component would generally also be
disabled and the installer would then only install that component if another
enabled component depended on it.
其余选项具有更专门的效果,仅适用于少数包生成器。安装类型是组件的预设选择,可用于简化用户在安装时必须做出的选择。可以使用该选项将组件分配给任意数量的安装类型INSTALL_TYPES,其中每个安装type类型都是由命令单独定义的名称,cpack_add_install_type()如下所示:
The remaining options have more specialized effects that apply to only a small
number of package generators. An install type is a preset selection of
components which can be used to simplify the choices a user has to make at
install time. A component can be assigned to any number of install types with
the INSTALL_TYPES option, where each type is a name that is defined
separately by the cpack_add_install_type() command like so:
cpack_add_install_type(typeName [DISPLAY_NAME uiName])cpack_add_install_type(typeName [DISPLAY_NAME uiName])
如果该选项已经具有足够的描述性,
DISPLAY_NAME则可以省略该选项,但对于应使用多个单词显示的安装类型,必须使用该选项并且将是带引号的字符串。没有预定义的安装类型,但通常会看到软件包提供名称如、或 的安装类型。在 CMake 提供的主动维护的包生成器中,只有 NSIS 支持安装类型功能。typeNameDISPLAY_NAMEuiNameFullMinimalDefault
The DISPLAY_NAME option can be omitted if typeName is already sufficiently
descriptive, but for install types that should be shown using multiple words,
DISPLAY_NAME must be used and uiName will be a quoted string. There are no
predefined install types, but it is common to see packages provide install
types with names like Full, Minimal or Default. Of the actively
maintained package generators provided by CMake, only NSIS supports the install
types feature.
对于那些支持可下载组件的生成器,添加
DOWNLOADED关键字可以cpack_add_component()使组件按需下载,而不是直接包含在包中。该
ARCHIVE_FILE选项可用于自定义可下载组件的文件名。CMake 提供的唯一支持可下载组件的主动维护生成器是 IFW,因此对此功能的讨论推迟到第 27.4.2 节“Qt 安装程序框架 (IFW)”。同样,该PLIST选项(仅适用于 CMake 3.9 或更高版本)仅由包生成器使用productbuild
(请参阅第 27.4.6 节“productbuild”)。
For those generators that support downloadable components, adding the
DOWNLOADED keyword to cpack_add_component() makes the component downloaded
on demand rather than being included in the package directly. The
ARCHIVE_FILE option can be used to customize the file name of the
downloadable component. The only actively maintained generator provided by
CMake that supports downloadable components is IFW, so discussion of this
feature is deferred to Section 27.4.2, “Qt Installer Framework (IFW)”. Similarly, the PLIST option (only
available with CMake 3.9 or later) is used exclusively by the productbuild
package generator (see Section 27.4.6, “productbuild”).
如果没有定义组件的GROUP详细信息,则组件将在大多数 UI 安装程序中充当简单的平面列表。使用分组时,它可以定义任意深度的层次结构,其中组可以包含组件和其他组。使用模块中的以下命令定义组CPackComponent:
If no components are defined with GROUP details, the components will act as a
simple flat list in most UI installers. When grouping is used, it enables an
arbitrarily deep hierarchical structure to be defined instead, where groups can
contain components and other groups. A group is defined using the following
command from the CPackComponent module:
cpack_add_component_group(groupName
[DISPLAY_NAME name]
[DESCRIPTION description]
[PARENT_GROUP parent]
[EXPANDED]
[BOLD_TITLE]
)cpack_add_component_group(groupName
[DISPLAY_NAME name]
[DESCRIPTION description]
[PARENT_GROUP parent]
[EXPANDED]
[BOLD_TITLE]
)
此命令可以出现在cpack_add_component()引用groupName. 和选项DISPLAY_NAME与命令DESCRIPTION中的对应选项具有相同的用途cpack_add_component()。该PARENT_GROUP选项与组等效GROUP,允许将其放置在另一个组下以支持任意组层次结构。给出关键字后EXPANDED,该组最初将在安装程序 UI 中展开,并且BOLD_TITLE关键字的存在将使该组显示为粗体。
This command can appear before or after cpack_add_component() calls that
refer to the groupName. The DISPLAY_NAME and DESCRIPTION options serve
the same purpose as their counterparts in the cpack_add_component() command.
The PARENT_GROUP is the group’s equivalent of the GROUP option, allowing it
to be placed under another group to support arbitrary group hierarchies. When
the EXPANDED keyword is given, the group will initially be expanded in the
installer UI and the presence of the BOLD_TITLE keyword will make that group
show up as bold.
理想情况下,组件名称应该是特定于项目的,以允许分层项目安排有效地选择要打包的组件以及如何在安装程序中呈现它们(或者在非 UI 安装程序的情况下,如何构建特定于组件的包)。组名称的限制较少,因为它们可能包含来自不同项目的组件和组。组名称不能与任何组件名称相同。
Component names should ideally be project specific to allow hierarchical project arrangements to effectively select which components to package and how to present them in installers (or in the case of non-UI installers, how to structure the component-specific packages). Group names are less restrictive, since they may contain components and groups from across different projects. A group name cannot be the same as any component name.
cpack_add_component()和的作用cpack_add_component_group()是在当前范围内定义一系列特定于组件的变量。文档
CPackComponent列出了其中一些变量,并建议可以直接设置这些变量,但不建议这样做。这些命令提供了一种更强大、更易读的方式来定义组件和组详细信息,应该是首选。它们还应该在与include(CPack)调用相同的范围内调用,最好是在调用之后立即调用。从技术上讲,约束并不那么严格,但在不同范围内定义组件详细信息可能会更加脆弱。
The effect of both cpack_add_component() and cpack_add_component_group() is
to define a range of component-specific variables in the current scope. The
CPackComponent documentation lists some of these variables and suggests that
the variables can be set directly, but this is not recommended. The commands
offer a more robust and more readable way of defining component and group
details and should be preferred. They should also be called in the same scope
as the include(CPack) call, ideally immediately after it. Technically the
constraint is not quite as strict as this, but defining the component details
in a different scope can be more fragile.
一个例子应该有助于巩固上述一些概念和讨论。
An example should help consolidate some of the above concepts and discussions.
set(CPACK_PACKAGE_NAME ...)
# ... set other variables as per earlier example
include(CPack)
cpack_add_component(MyProj_Runtime
DISPLAY_NAME Runtime
DESCRIPTION "Shared libraries and executables"
REQUIRED
INSTALL_TYPES Full Developer Minimal
)
cpack_add_component(MyProj_Development
DISPLAY_NAME "Developer pre-requisites"
DESCRIPTION "Headers/static libs needed for building"
DEPENDS MyProj_Runtime
GROUP MyProj_SDK
INSTALL_TYPES Full Developer
)
cpack_add_component(MyProj_Samples
DISPLAY_NAME "Code samples"
GROUP MyProj_DevHelp
INSTALL_TYPES Full Developer
DISABLED
)
cpack_add_component(MyProj_ApiDocs
DISPLAY_NAME "API documentation"
GROUP MyProj_DevHelp
INSTALL_TYPES Full Developer
DISABLED
)
cpack_add_component_group(MyProj_SDK
DISPLAY_NAME SDK
DESCRIPTION "Developer tools, libraries, etc."
)
cpack_add_component_group(MyProj_DevHelp
DISPLAY_NAME Documentation
DESCRIPTION "Code samples and API docs"
PARENT_GROUP MyProj_SDK
)
cpack_add_install_type(Full)
cpack_add_install_type(Minimal)
cpack_add_install_type(Developer
DISPLAY_NAME "SDK Development"
)set(CPACK_PACKAGE_NAME ...)
# ... set other variables as per earlier example
include(CPack)
cpack_add_component(MyProj_Runtime
DISPLAY_NAME Runtime
DESCRIPTION "Shared libraries and executables"
REQUIRED
INSTALL_TYPES Full Developer Minimal
)
cpack_add_component(MyProj_Development
DISPLAY_NAME "Developer pre-requisites"
DESCRIPTION "Headers/static libs needed for building"
DEPENDS MyProj_Runtime
GROUP MyProj_SDK
INSTALL_TYPES Full Developer
)
cpack_add_component(MyProj_Samples
DISPLAY_NAME "Code samples"
GROUP MyProj_DevHelp
INSTALL_TYPES Full Developer
DISABLED
)
cpack_add_component(MyProj_ApiDocs
DISPLAY_NAME "API documentation"
GROUP MyProj_DevHelp
INSTALL_TYPES Full Developer
DISABLED
)
cpack_add_component_group(MyProj_SDK
DISPLAY_NAME SDK
DESCRIPTION "Developer tools, libraries, etc."
)
cpack_add_component_group(MyProj_DevHelp
DISPLAY_NAME Documentation
DESCRIPTION "Code samples and API docs"
PARENT_GROUP MyProj_SDK
)
cpack_add_install_type(Full)
cpack_add_install_type(Minimal)
cpack_add_install_type(Developer
DISPLAY_NAME "SDK Development"
)
可以要求项目生成器以三种方式之一处理组件,该选择由CPACK_COMPONENTS_GROUPING变量控制,该变量可以设置为以下值之一:
Project generators can be asked to process components in one of three ways, the
choice being controlled by the CPACK_COMPONENTS_GROUPING variable which
can be set to one of the following values:
ALL_COMPONENTS_IN_ONE
ALL_COMPONENTS_IN_ONE
ONE_PER_GROUP
ONE_PER_GROUP
CPACK_COMPONENTS_GROUPING如果未设置,则这是默认设置,通常是理想的安排,但对于某些 UI 安装程序,它隐藏了项目可能更喜欢显示的组件。
CPACK_COMPONENTS_GROUPING is not set and is usually
the desirable arrangement, but for some UI installers it hides components that
projects may prefer be shown.
IGNORE
IGNORE
另外两个变量也会影响生成器解释组件的方式。如果
CPACK_MONOLITHIC_INSTALL设置为 true,则组件将完全禁用,所有组件都将安装并捆绑到单个包中。这是一个相当残酷的切换,因此请在所有相关平台上仔细测试结果,特别注意寻找任何意外的文件。由于遗留原因,每个生成器还有自己的设置来默认是否支持组件。此设置可以在每个生成器的基础上由CPACK_<GENNAME>_COMPONENT_INSTALL变量覆盖,可以根据需要将其设置为 true 或 false。
Two more variables also affect how generators interpret components. If
CPACK_MONOLITHIC_INSTALL is set to true, components are disabled
completely and all components are installed and bundled into a single package.
This is a fairly brutal switch, so test the results carefully on all relevant
platforms, paying special attention to look out for any unexpected files.
For legacy reasons, each generator also has its own setting for whether or not
components are supported by default. This setting can be overridden on a
per-generator basis by the CPACK_<GENNAME>_COMPONENT_INSTALL variable,
which can be set to true or false as needed.
执行基于组件的安装时,项目不需要在最终包中包含所有组件。将包含的组件集由CPACK_COMPONENTS_ALL变量控制,该变量必须在调用之前设置include(CPack)。未设置时,cpack打包所有组件,但项目可以显式设置此变量以仅列出它想要打包的组件。例如,如果一个项目想要控制文档和代码示例是否应该打包,可以这样实现:
When performing a component-based install, projects are not required to include
all components in the final package(s). The set of components that will be
included are controlled by the CPACK_COMPONENTS_ALL variable, which must
be set before the call to include(CPack). When not set, cpack packages all
components, but the project can explicitly set this variable to only list the
components it wants packaged. For example, if a project wanted to control
whether documentation and code samples should be packaged, it could be achieved
like so:
if(NOT MYPROJ_PACKAGE_HELP)
set(CPACK_COMPONENTS_ALL
MyProj_Runtime
MyProj_Development
)
endif()
include(CPack)if(NOT MYPROJ_PACKAGE_HELP)
set(CPACK_COMPONENTS_ALL
MyProj_Runtime
MyProj_Development
)
endif()
include(CPack)
项目可能希望安装除少数特定组件之外的所有组件,而不是显式列出要打包的所有组件。完整的组件集在只读伪属性中可用COMPONENTS,只能通过get_cmake_property()命令检索。该项目可以从该组件列表开始,然后删除不需要的条目。
Rather than explicitly listing all the components to be packaged, a project may
want to install all but a few specific components. The full set of components
is available in the read-only pseudo property COMPONENTS, which can only be
retrieved via the get_cmake_property() command. The project can start
with that list of components and then remove the unwanted entries.
if(NOT MYPROJ_PACKAGE_HELP)
get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS)
list(REMOVE_ITEM CPACK_COMPONENTS_ALL
MyProj_Samples
MyProj_ApiDocs
)
endif()
include(CPack)if(NOT MYPROJ_PACKAGE_HELP)
get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS)
list(REMOVE_ITEM CPACK_COMPONENTS_ALL
MyProj_Samples
MyProj_ApiDocs
)
endif()
include(CPack)
选择安装哪组组件以及如何处理这些组件乍一看可能有点复杂。在实践中,造成困难的主要领域是理解每个包生成器如何处理 的不同值CPACK_COMPONENTS_GROUPING。本章后面的部分解释了每种生成器类型的行为,但是在测试项目上进行一些快速实验通常可以为了解不同设置的影响提供指导。
The selection of which set of components to install and how the components
should be handled may seem a little complex at first. In practice, the main
area that causes difficulty is understanding how each package generator
handles the different values of CPACK_COMPONENTS_GROUPING. The later sections
in this chapter explain the behavior of each generator type, but some quick
experiments on a test project can often be just as instructional for coming to
terms with the effects of the different settings.
CPack 主要用于为单个构建配置生成包。在大多数情况下,包是为Release构建类型创建的,但对于 SDK 项目之类的东西,可能需要包含库的调试版本和发布版本,特别是对于多配置生成器。
CPack is primarily geared towards producing packages for a single build
configuration.
In most cases, packages are created for the Release build type, but for
things like SDK projects, it may be desirable to include both debug and release
versions of libraries, especially for multi-config generators.
创建多配置包有两种主要方法。第一种方法可以适用于单配置生成器和多配置生成器,并且已经在 CMake 中可用很长时间了。它的设置比较复杂,但它也支持其他有趣的场景。第二种方法仅支持多配置生成器,并且仅适用于 CMake 3.16 或更高版本,但设置和使用非常简单。
There are two main approaches to creating a multi-config package. The first method can be adapted to work for both single and multi-config generators and has been available in CMake for a long time. It is more complex to set up, but it also supports other interesting scenarios. The second method only supports multi-config generators and is only available with CMake 3.16 or later, but it is very straightforward to setup and use.
CPack 提供了高级变量CPACK_INSTALL_CMAKE_PROJECTS,可用于将多个构建树合并到一次打包运行中。预计将容纳一个或多个四元组,其中每个四元组包含:
CPack provides the advanced variable CPACK_INSTALL_CMAKE_PROJECTS which
can be used to incorporate multiple build trees into the one packaging run. It
is expected to hold one or more quadruples where each quadruple consists of:
ALL表示安装变量中列出的组件CMAKE_COMPONENTS_ALL。其他值需要CMAKE_COMPONENTS_XXX定义一个类似的变量,该变量仅保存该一个组件名称。例如,如果要安装的组件被调用,则需要定义Runtime一个变量并具有值。CMAKE_COMPONENTS_RUNTIMERuntime
ALL means to install the
components listed in the CMAKE_COMPONENTS_ALL variable. Other values
require a similar CMAKE_COMPONENTS_XXX variable to be defined which holds
just that one component name. For example, if the component to install was
called Runtime, then a variable CMAKE_COMPONENTS_RUNTIME would need to be
defined and have the value Runtime.
/由于不同的包生成器使用它的方式不同,唯一安全的值是单个正斜杠 ( )。
/) due to the way different package
generators use it.
该项目可以定义四组,一组用于发布版本,其余用于调试版本。发布构建的构建目录可以简单地是CMAKE_BINARY_DIR,但对于调试构建,需要创建并构建第二个单独的构建目录。
The project can define sets of quadruples, one for the release build and the
rest for the debug build. The build directory for the release build can simply
be CMAKE_BINARY_DIR, but for the debug build, a second separate build
directory needs to have been created and built.
调试四元组只需要添加两个构建配置之间不同的组件,但无论使用默认ALL
组件还是使用特定组件,都需要特别小心,以确保安装的文件不会意外地相互覆盖。最后列出发布组件将确保任何具有相同名称和安装位置的文件在打包时最终都会包含发布版本。
The debug quadruples would only need to add those components that are different
between the two build configurations, but whether using the default ALL
component or using specific components, special care needs to be exercised to
ensure installed files don’t unexpectedly overwrite each other. Listing the
release component last will ensure that any files that have the same name and
install location will end up with the release version when packaged.
set(CPACK_COMPONENTS_MYPROJ_RUNTIME MyProj_Runtime)
set(CPACK_COMPONENTS_MYPROJ_DEVELOPMENT MyProj_Development)
unset(CPACK_INSTALL_CMAKE_PROJECTS)
if(MYPROJ_DEBUG_BUILD_DIR)
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS
# Runtime debug component
${MYPROJ_DEBUG_BUILD_DIR}
${CMAKE_PROJECT_NAME}
MyProj_Runtime
/
# Development debug component
${MYPROJ_DEBUG_BUILD_DIR}
${CMAKE_PROJECT_NAME}
MyProj_Development
/
)
endif()
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS
${CMAKE_BINARY_DIR} ${CMAKE_PROJECT_NAME} ALL /
)
include(CPack)set(CPACK_COMPONENTS_MYPROJ_RUNTIME MyProj_Runtime)
set(CPACK_COMPONENTS_MYPROJ_DEVELOPMENT MyProj_Development)
unset(CPACK_INSTALL_CMAKE_PROJECTS)
if(MYPROJ_DEBUG_BUILD_DIR)
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS
# Runtime debug component
${MYPROJ_DEBUG_BUILD_DIR}
${CMAKE_PROJECT_NAME}
MyProj_Runtime
/
# Development debug component
${MYPROJ_DEBUG_BUILD_DIR}
${CMAKE_PROJECT_NAME}
MyProj_Development
/
)
endif()
list(APPEND CPACK_INSTALL_CMAKE_PROJECTS
${CMAKE_BINARY_DIR} ${CMAKE_PROJECT_NAME} ALL /
)
include(CPack)
当使用 Xcode、Visual Studio 或 Ninja Multi-Config 等多配置生成器时,MYPROJ_DEBUG_BUILD_DIR需要将上例中的目录配置为仅支持Debug构建类型,而不是通常的默认集。这是强制它安装调试构建输出的唯一方法。cmake在该调试构建目录中
运行时,显式地将CMAKE_CONFIGURATION_TYPES缓存变量设置为Debug以获得必要的安排。
When using multi configuration generators like Xcode, Visual Studio or Ninja
Multi-Config, the MYPROJ_DEBUG_BUILD_DIR directory in the above example needs
to be configured to support only the Debug build type rather than the usual
default set.
This is the only way to force it to install debug build outputs.
When running cmake in that debug build directory, explicitly set the
CMAKE_CONFIGURATION_TYPES cache variable to Debug to get the necessary
arrangement.
虽然可以仅使用一个构建目录来实现多配置生成器,但这样做的技术更加脆弱和复杂。相反,上述技术适用于所有构建和包生成器类型。此外,它还可以扩展以将不同架构的构建合并到一个统一的包中,甚至可以将完全独立的项目合并到一起。
While it is possible to use just the one build directory for multi configuration generators, the techniques to do so are more fragile and complex. In contrast, the above technique works for all build and package generator types. Furthermore, it can be extended to incorporate builds for different architectures or even completely separate projects into one unified package.
该cpack工具提供了一个-C命令行选项,可用于指定要打包的配置。该选项仅对多配置生成器有用。从 CMake 3.16 开始,可以为该选项提供多个配置作为分号分隔的列表。
cpack然后会将所有列出的配置添加到包中。
The cpack tool provides a -C command line option which can be used to
specify the configuration to package.
This option only has a useful meaning for multi-config generators.
From CMake 3.16, more than one configuration can be given to that option as a
semicolon-separated list.
cpack will then add all the listed configurations to the package.
此功能仅在直接调用时可用cpack,使用构建目标不可用package,因为构建本质上是单个配置。应该注意的是,这意味着用户有责任确保在调用之前已经构建了列出的所有配置cpack。所需步骤的典型示例如下所示:
This functionality is only available when invoking cpack directly, it is not
available using the package build target because a build is inherently a
single configuration.
It should be noted that this means the user is responsible for ensuring that
all configurations listed have already been built before invoking cpack.
A typical example of the steps required looks like this:
cd /path/to/build/目录 cmake --build 。--配置调试 cmake --build 。--config 发布 cpack -C“调试;发布”
cd /path/to/build/directory cmake --build . --config Debug cmake --build . --config Release cpack -C "Debug;Release"
该方法的一个显着优点是其简单性。不需要额外的设置,并且步骤很容易编写脚本。当使用多配置生成器和 CMake 3.16 或更高版本时,这可能是首选方法。
A significant advantage of this method is its simplicity. No additional setup is required and the steps are easily scripted. When using a multi-config generator and CMake 3.16 or later, this is likely to be the preferred approach.
CPack 可以生成多种包格式,每种格式属于以下类别之一:
CPack can generate a variety of package formats, each falling into one of the following categories:
无论使用哪个包生成器,CPackConfig.cmake
都会处理相同的文件。这通常不会出现问题,因为特定于生成器的配置通常可以在需要时通过特定于生成器的变量来实现。如果仅需要为特定生成器添加某些逻辑,并且 CMake 和 CPack 提供的现有变量不足,则
CPACK_PROJECT_CONFIG_FILE可以将变量设置为文件的名称,该文件将在每个被调用的包生成器中包含一次。每次读取时,该CPACK_GENERATOR变量将保存正在处理的生成器的名称,而不是整个生成器列表。这允许该文件覆盖CPackConfig.cmake仅为那些需要它的特定生成器所做的设置。完整的cpack运行大致遵循以下伪代码中的步骤:
Regardless of which package generators are used, the same CPackConfig.cmake
file is processed.
This doesn’t generally present an issue, since generator-specific configuration
is normally made possible through generator-specific variables where needed.
If certain logic needs to be added for only a particular generator and the
existing variables offered by CMake and CPack are insufficient, the
CPACK_PROJECT_CONFIG_FILE variable can be set to the name of a file that
will be included once for each package generator being invoked.
Each time it is read, the CPACK_GENERATOR variable will hold the name of
the generator being processed rather than the whole list of generators.
This allows that file to override settings made in CPackConfig.cmake for only
those specific generators that require it.
The full cpack run loosely follows the steps in the following pseudo code:
include(CPackConfig.cmake)
function(generate CPACK_GENERATOR)
# CPACK_GENERATOR is a single generator local to this
# function scope
if(CPACK_PROJECT_CONFIG_FILE)
include(${CPACK_PROJECT_CONFIG_FILE})
endif()
# ...invoke package generator
endfunction()
# Here CPACK_GENERATOR is the list of generators to be
# processed, as set by CPackConfig.cmake or on the cpack
# command line
foreach(generator IN LISTS CPACK_GENERATOR)
generate(${generator})
endforeach()include(CPackConfig.cmake)
function(generate CPACK_GENERATOR)
# CPACK_GENERATOR is a single generator local to this
# function scope
if(CPACK_PROJECT_CONFIG_FILE)
include(${CPACK_PROJECT_CONFIG_FILE})
endif()
# ...invoke package generator
endfunction()
# Here CPACK_GENERATOR is the list of generators to be
# processed, as set by CPackConfig.cmake or on the cpack
# command line
foreach(generator IN LISTS CPACK_GENERATOR)
generate(${generator})
endforeach()
上述内容有用的一个示例是设置CPACK_PACKAGE_ICON为生成器特定值,因为不同的生成器期望该图标采用不同的格式,因此文件名需要是生成器特定的。
An example where the above can be useful is to set CPACK_PACKAGE_ICON to
a generator specific value, since different generators expect this icon to be
in different formats and therefore the file name needs to be generator
specific.
本章的其余部分讨论 CMake/CPack 提供的每个主动维护的包生成器。
The remainder of this chapter discusses each of the actively maintained package generators provided by CMake/CPack.
CPack 支持创建多种不同格式的档案。最广泛支持的是ZIP和TGZ,前者常见于 Windows 平台,后者生成 gzipped tarball(.tar.gz或
.tgz),基本上在其他地方都受支持。其他可用的存档格式包括TBZ2( .tar.bz2)、TXZ( .tar.xz)、
TZ( .tar.Z) 和7Z(7zip 存档,.7z)。CMake 3.16 还添加了对使用
TZST( .tar.zst) 格式的 Zstandard 压缩的 tarball 的支持。为了获得最大的可移植性,ZIP通常TGZ应该首选 ,但其他一些格式可能会产生较小的档案,并且可能适合通常支持这些格式的平台。
CPack supports the creation of archives in a number of different formats.
The most widely supported are ZIP and TGZ, the former being common for
Windows platforms and the latter producing gzipped tarballs (.tar.gz or
.tgz) that are supported essentially everywhere else.
Other available archive formats include TBZ2 (.tar.bz2), TXZ (.tar.xz),
TZ (.tar.Z) and 7Z (7zip archives, .7z).
CMake 3.16 also added support for tarballs using Zstandard compression with the
TZST (.tar.zst) format.
For maximum portability, ZIP and TGZ should generally be preferred, but
some of the other formats may produce smaller archives and may be suitable for
platforms where those formats are commonly supported.
.还支持自解压存档格式cpack。这可以使用生成器 name 来请求STGZ,它会生成一个 Unix shell 脚本,并在该脚本的末尾嵌入存档。这可以被认为是一种基于控制台的 UI 安装程序,但实际上它只提供非常基本的功能,用户可能更喜欢可以自行解压的简单存档。
A self-extracting archive format is also supported by cpack. This can be
requested using the generator name STGZ, which produces a Unix shell script
with the archive embedded at the end of that script. This can be thought of as
a form of console-based UI installer, but in practice it offers only very basic
functionality and users may prefer a simple archive that they can unpack
themselves.
由于遗留原因,存档生成器默认禁用组件。要启用基于组件的存档创建,CPACK_ARCHIVE_COMPONENT_INSTALL
必须设置为 true,然后CPACK_COMPONENTS_GROUPING确定将生成的存档文件集。
For legacy reasons, archive generators have components disabled by default. To
enable component-based archive creation, CPACK_ARCHIVE_COMPONENT_INSTALL
must be set to true and then CPACK_COMPONENTS_GROUPING will determine the set
of archive files that will be generated.
执行非组件安装时,可以使用CPACK_ARCHIVE_FILE_NAME变量控制最终的包文件名。对于基于组件的安装,每个组件的包的名称由 控制
CPACK_ARCHIVE_<COMP>_FILE_NAME,其中<COMP>是大写的组件或组名称。适当的存档扩展名将附加到指定的文件名(即.tar.gz、.zip等)。
When performing a non-component install, the final package file name can be
controlled using the CPACK_ARCHIVE_FILE_NAME variable. For
component-based installs, the name of each component’s package is controlled by
CPACK_ARCHIVE_<COMP>_FILE_NAME, where <COMP> is the uppercased
component or group name. The appropriate archive extension will be appended to
the specified file name (i.e. .tar.gz, .zip, etc.).
归档文件的一个常见约定是使提取的目录结构的顶层与归档文件的名称相同,但不带文件扩展名(即与 相同CPACK_PACKAGE_FILE_NAME)。对于非组件安装,这已经是存档生成器的默认行为,但对于多组件包,默认情况下不使用顶级目录。项目可以通过设置为 true 来强制组件归档使用公共顶级目录CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY。由于此变量由所有包生成器共享,因此特定于生成器的覆盖将是执行此操作的最合适方法:
A common convention for archive files is to make the top level of the extracted
directory structure be the same as the name of the archive file without the
file extension (i.e. the same as CPACK_PACKAGE_FILE_NAME). For non-component
installs, this is already the default behavior for the archive generators, but
for multi component packages, no top level directory is used by default.
Projects can enforce a common top level directory for component archives by
setting CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY to true. Since this
variable is shared by all package generators, a generator specific override
would be the most appropriate way to do this:
set(CPACK_PROJECT_CONFIG_FILE
${CMAKE_CURRENT_LIST_DIR}/cpackGeneratorOverrides.cmake
)set(CPACK_PROJECT_CONFIG_FILE
${CMAKE_CURRENT_LIST_DIR}/cpackGeneratorOverrides.cmake
)
if(CPACK_GENERATOR MATCHES
"^(7Z|TBZ2|TGZ|TXZ|TZ|TZST|ZIP)$")
set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY YES)
endif()if(CPACK_GENERATOR MATCHES
"^(7Z|TBZ2|TGZ|TXZ|TZ|TZST|ZIP)$")
set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY YES)
endif()
开发人员应注意,某些存档格式、平台和文件系统对文件名和路径的长度有限制。例如,对于扩展的 tar 交换格式,POSIX.2 要求文件名不得超过 100 个字符,路径不得超过 255 个字符,而较旧的 tar 格式可能将整个路径限制为 100 个字符或更少。将存档解压到 eCryptFS 文件系统时,根据经验得出的文件名长度限制约为 140 个字符。Windows 上的解包路径长度限制为 260 个字符,具体取决于某些设置和操作系统版本。UTF-8 文件名和路径使情况变得更加复杂,并且可能进一步缩短有效字符限制。考虑到这些限制,项目应避免在其包内容中使用长路径和文件名。这些限制对于存档包类型最为明显,但由于其他非存档格式也在内部使用存档并部署到具有这些限制的系统,因此通常应首选较短的路径和文件名。
Developers should note that some archive formats, platforms and file systems have limitations on the length of file names and paths. For example, POSIX.2 requires file names to be 100 characters or less and paths to be 255 characters or less for the extended tar interchange format, while older tar formats may restrict the entire path to 100 characters or less. When unpacking an archive onto an eCryptFS file system, file names have an empirically derived limit of about 140 characters. Unpacking on Windows can have a 260 character path length limit, depending on certain settings and OS version. UTF-8 file names and paths further complicate the picture and may shorten the effective character limits even more. With these constraints in mind, projects should avoid using long paths and file names in their package contents. These restrictions are most evident with archive package types, but since other non-archive formats also use archives internally and deploy to systems with these restrictions, shorter paths and file names should be preferred in general.
IFW 包生成器为 CPack 提供的所有基于 UI 的包格式提供最广泛的平台支持。可以使用相同的配置详细信息为 Windows、Mac 和 Linux 构建安装程序,当项目希望在所有主要桌面平台上拥有一致的 UI 安装程序时,它是一个不错的选择。它还具有易于使用的组件和组显示名称和描述的本地化以及广泛的可定制性。
The IFW package generator offers the broadest platform support of all UI-based package formats provided by CPack. Installers can be built for Windows, Mac and Linux from the same configuration details, making it a good choice when a project wants to have a consistent UI installer across all major desktop platforms. It also has easy to use localization of component and group display names and descriptions as well as extensive customizability.
UI 外观和安装程序图标的默认设置通常就足够了,但某些项目可能需要自定义一些方面来改进品牌,尤其是围绕图标的使用。该生成器会忽略该CPACK_PACKAGE_ICON变量,而是依赖三个单独的 IFW 特定变量来控制不同上下文的图标:
The defaults for the UI appearance and installer icons are often sufficient,
but some projects may want to customize a few aspects to improve the branding,
especially around the use of icons. The CPACK_PACKAGE_ICON variable is
ignored for this generator, which relies instead on three separate IFW-specific
variables to control the icons for different contexts:
CPACK_IFW_PACKAGE_ICON(.ico对于 Windows、.icnsMac、Linux 忽略)
CPACK_IFW_PACKAGE_ICON (.ico for Windows, .icns for Mac, ignored
for Linux)
CPACK_IFW_PACKAGE_WINDOW_ICON(总是.png)
CPACK_IFW_PACKAGE_WINDOW_ICON (always .png)
CPACK_IFW_PACKAGE_LOGO(最好.png)
CPACK_IFW_PACKAGE_LOGO (preferably .png)
不幸的是,这些变量在平台之间的处理方式不一致,因此很难正确设置它们。为简单起见,最好将所有三个图像设置为同一图像,尽管可能采用不同的格式和/或大小。建议在每个感兴趣的平台上进行测试,以确保安装程序按预期呈现。以下示例显示了如何指定此类配置:
Unfortunately, these variables are not handled consistently between platforms, so it can be difficult to set them correctly. For simplicity, it may be preferable to set all three to the same image, albeit potentially in different formats and/or sizes. Testing on each platform of interest is recommended to ensure the installer presents itself as expected. The following example shows how such a configuration may be specified:
# Define generic setup for all generator types...
# IFW-specific configuration
if(WIN32)
set(CPACK_IFW_PACKAGE_ICON
${CMAKE_CURRENT_LIST_DIR}/Logo.ico
)
elseif(APPLE)
set(CPACK_IFW_PACKAGE_ICON
${CMAKE_CURRENT_LIST_DIR}/Logo.icns
)
endif()
set(CPACK_IFW_PACKAGE_WINDOW_ICON
${CMAKE_CURRENT_LIST_DIR}/Logo.png
)
set(CPACK_IFW_PACKAGE_LOGO
${CMAKE_CURRENT_LIST_DIR}/Logo.png
)
include(CPack)
include(CPackIFW)
# Define components and component groups...# Define generic setup for all generator types...
# IFW-specific configuration
if(WIN32)
set(CPACK_IFW_PACKAGE_ICON
${CMAKE_CURRENT_LIST_DIR}/Logo.ico
)
elseif(APPLE)
set(CPACK_IFW_PACKAGE_ICON
${CMAKE_CURRENT_LIST_DIR}/Logo.icns
)
endif()
set(CPACK_IFW_PACKAGE_WINDOW_ICON
${CMAKE_CURRENT_LIST_DIR}/Logo.png
)
set(CPACK_IFW_PACKAGE_LOGO
${CMAKE_CURRENT_LIST_DIR}/Logo.png
)
include(CPack)
include(CPackIFW)
# Define components and component groups...
默认情况下,IFW 生成器启用基于组件的安装。始终生成单个安装程序,但CPACK_COMPONENTS_GROUPING控制向用户显示的组件层次结构的数量:
Component-based installation is enabled by default for the IFW generator. A
single installer is always produced, but CPACK_COMPONENTS_GROUPING controls
how much of the component hierarchy is shown to the user:
ALL_COMPONENTS_IN_ONE
ALL_COMPONENTS_IN_ONE
ONE_PER_GROUP
ONE_PER_GROUP
IGNORE
IGNORE
除了通用命令提供的功能之外,还可以进一步配置组件和组:
Components and groups can be further configured beyond what the generic commands provide:
cpack_ifw_configure_component(componentName
[NAME componentNameId]
[DISPLAY_NAME displayName...]
[DESCRIPTION description...]
[VERSION <version>]
[DEPENDS compId1 [compId2...] ] ①
[REPLACES compId3 [compId4...] ]
# Other options not shown
)
# The cpack_ifw_configure_component_group() command
# supports the same optionscpack_ifw_configure_component(componentName
[NAME componentNameId]
[DISPLAY_NAME displayName...]
[DESCRIPTION description...]
[VERSION <version>]
[DEPENDS compId1 [compId2...] ] ①
[REPLACES compId3 [compId4...] ]
# Other options not shown
)
# The cpack_ifw_configure_component_group() command
# supports the same options
DEPENDENCIES也被接受,但更喜欢DEPENDS与其他 CMake 命令保持一致。DEPENDENCIES is also accepted, but prefer DEPENDS for consistency with
other CMake commands.每个组件或组的和可以针对不同的语言和DISPLAY_NAME区域DESCRIPTION设置提供替代内容。这两个选项接受一个对列表,其中对的第一个值是语言或区域设置 ID,第二个值是该语言的文本。列表中的第一个值可以在没有前面的语言或区域设置 ID 的情况下给出,如果没有任何语言或区域设置 ID 与安装时用户的当前设置匹配,则该值将用作默认文本。
The DISPLAY_NAME and DESCRIPTION of each component or group can be given
alternative contents for different languages and locales.
These two options accept a list of pairs where the first value of a pair is
the language or locale ID and the second value is the text for that language.
The first value in the list can be given without a preceding language or
locale ID and it will be used as the default text if none of the languages or
locale IDs match the user’s current setting at install time.
cpack_ifw_configure_component(MyProj_Docs
DISPLAY_NAME Documentation
de Dokumentation
pl Dokumentacja
)
cpack_ifw_configure_component_group(MyProj_Colors
DISPLAY_NAME en Colors
en_AU Colours
DESCRIPTION en "Available color palettes"
en_AU "Available colour palettes"
)cpack_ifw_configure_component(MyProj_Docs
DISPLAY_NAME Documentation
de Dokumentation
pl Dokumentacja
)
cpack_ifw_configure_component_group(MyProj_Colors
DISPLAY_NAME en Colors
en_AU Colours
DESCRIPTION en "Available color palettes"
en_AU "Available colour palettes"
)
该VERSION选项允许指定每个组件和每个组的版本号。在线安装程序使用它来确定更新是否可用(请参阅下文)。如果VERSION未给出,则默认为
CPACK_PACKAGE_VERSION.
The VERSION option allows per-component and per-group version numbers to be
specified. This is used by online installers to determine whether an update is
available (see further below). If VERSION is not given, it defaults to
CPACK_PACKAGE_VERSION.
该DEPENDS选项与 中的相同选项类似,cpack_add_component()
只是条目的形式compId1…不同。这些需要遵循 QtIFW 风格,这是一个分层字符串而不是原始
componentName. 分组层次结构的每个级别均以点分隔,如以下示例所示:
The DEPENDS option is analogous to the same option in cpack_add_component()
except that the form of the compId1… entries is different. These need to
follow the QtIFW style, which is a hierarchical string rather than a raw
componentName. Each level of the grouped hierarchy is dot-separated, as
demonstrated by the following example:
include(CPack)
include(CPackIFW)
cpack_add_component(foo GROUP groupA)
cpack_add_component(bar GROUP groupB)
cpack_add_component_group(groupA)
cpack_add_component_group(groupB)
cpack_ifw_configure_component(bar DEPENDS groupA.foo)include(CPack)
include(CPackIFW)
cpack_add_component(foo GROUP groupA)
cpack_add_component(bar GROUP groupB)
cpack_add_component_group(groupA)
cpack_add_component_group(groupB)
cpack_ifw_configure_component(bar DEPENDS groupA.foo)
还可以向该DEPENDS值附加版本要求。=这可以通过附加分隔符(稍后讨论)、运算符( 、<、<=或>之一>=)和版本号来实现。从技术上讲,QtIFW 格式允许省略运算符,这应该会导致使用默认运算符=。CMake 3.20 及更早版本包含一个错误,该错误会导致在未给出运算符时删除版本号。因此,如果在 IFW 组件依赖项上包含版本约束,则最好始终指定运算符。
One can also append a version requirement to the DEPENDS value.
This can be achieved by appending a separator (discussed shortly), an operator
(one of =, <, <=, > or >=) and the version number.
Technically the QtIFW format allows the operator to be omitted, which should
result in using a default operator of =.
CMake 3.20 and earlier contains a bug which causes the version number to be
dropped when no operator is given.
Therefore, prefer to always specify an operator if including a version
constraint on an IFW component dependency.
包名称和运算符之间的分隔符可以是冒号 ( :) 或连字符 ( -)。冒号更具可读性,并且具有组件和组名称可以包含连字符的优点。仅当使用 CMake 3.21 或更高版本以及 QtIFW 3.1 或更高版本时,才能使用冒号作为分隔符。如果使用较旧的 CMake 或 QtIFW 版本,则必须使用连字符作为分隔符,并且组件和组名称不能包含任何连字符。
The separator between the package name and operator can be either a colon (:)
or a hyphen (-).
Colons are more readable and have the advantage that component and group names
can contain hyphens.
A colon can only be used as the separator if using CMake 3.21 or later and
QtIFW 3.1 or later.
If using older CMake or QtIFW versions, a hyphen must be used as the separator
and the component and group names cannot contain any hyphens.
# Colons as separators, supports hyphens in names
# NOTE: Requires CMake 3.21+ and QtIFW 3.1+.
cpack_ifw_configure_component(baz DEPENDS a-b-c:=7.8.2)
cpack_ifw_configure_component(baz DEPENDS old-thing:<3)
cpack_ifw_configure_component(baz DEPENDS newbie:>=6)
# Hyphens as separators, no hyphens in names
cpack_ifw_configure_component(baz DEPENDS exacto-=7.8.2)
cpack_ifw_configure_component(baz DEPENDS oldie-<3)
cpack_ifw_configure_component(baz DEPENDS newbie->=6)# Colons as separators, supports hyphens in names
# NOTE: Requires CMake 3.21+ and QtIFW 3.1+.
cpack_ifw_configure_component(baz DEPENDS a-b-c:=7.8.2)
cpack_ifw_configure_component(baz DEPENDS old-thing:<3)
cpack_ifw_configure_component(baz DEPENDS newbie:>=6)
# Hyphens as separators, no hyphens in names
cpack_ifw_configure_component(baz DEPENDS exacto-=7.8.2)
cpack_ifw_configure_component(baz DEPENDS oldie-<3)
cpack_ifw_configure_component(baz DEPENDS newbie->=6)
可以使用该选项覆盖安装程序内部使用的组件名称NAME。该名称将用于标识
DEPENDS参数中的组件,以及检查组件的较新版本是否可用时。顶级组名称可以使用该
CPACK_IFW_PACKAGE_GROUP变量进行设置,并且通常设置为反向域名,以确保组件名称不会在大型多供应商安装程序中发生冲突。当使用该选项列出依赖项时,必须包含此顶级组名称DEPENDS,如前面示例的以下修改所示:
The name used internally within the installer for a component can be overridden
with the NAME option. This name would be used to identify the component in
DEPENDS arguments and also when checking if a newer version of a component is
available. A top level group name can be set with the
CPACK_IFW_PACKAGE_GROUP variable and is often set to a reverse domain
name to ensure component names don’t clash in large, multi-vendor installers.
This top level group name must then be included when listing dependencies with
the DEPENDS option, as the following modification of the earlier example
shows:
set(CPACK_IFW_PACKAGE_GROUP com.examplecompany.product)
include(CPack)
include(CPackIFW)
cpack_add_component(foo GROUP groupA)
cpack_add_component(bar GROUP groupB)
cpack_add_component_group(groupA)
cpack_add_component_group(groupB)
cpack_ifw_configure_component(bar
DEPENDS com.examplecompany.product.groupA.foo
)set(CPACK_IFW_PACKAGE_GROUP com.examplecompany.product)
include(CPack)
include(CPackIFW)
cpack_add_component(foo GROUP groupA)
cpack_add_component(bar GROUP groupB)
cpack_add_component_group(groupA)
cpack_add_component_group(groupB)
cpack_ifw_configure_component(bar
DEPENDS com.examplecompany.product.groupA.foo
)
CPACK_IFW_PACKAGE_GROUP这只是可以设置以提供特定于 IFW 的配置的大量额外变量之一。此类变量应在调用之前设置include(CPackIFW),并且可以通过多种方式修改安装程序的外观和行为。模块CPackIFW
文档提供了所有支持的变量及其效果的完整列表,其中许多变量在 QtIFW 产品的本机配置设置中具有类似的设置。大多数这些变量都有合理的默认值,应该更多地被视为定制的机会,而不是需要设置的东西。一个例外是与与产品其余部分一起安装的维护工具的名称相关的变量,它允许用户修改一组已安装的组件或完全删除该产品。默认情况下,该工具的名称为maintenancetool,但这并未表明该工具的相关内容。在某些平台上,工具名称可能会显示在桌面或应用程序菜单中,而默认名称可能会让用户感到困惑。因此,项目应该提供一个更具体的名称,可以像这样完成:
CPACK_IFW_PACKAGE_GROUP is just one example of a large number of extra
variables that can be set to provide IFW-specific configuration. Such variables
should be set before include(CPackIFW) is called and can modify the
appearance and behavior of the installer in a variety of ways. The CPackIFW
module documentation provides a complete listing of all supported variables and
their effects, many of which have analogous settings in the QtIFW product’s
native configuration settings. Most of those variables have sensible defaults
and should be seen more as opportunities for customization rather than things
that need to be set. One exception to this is the variables relating to the
name of the maintenance tool installed along with the rest of the product,
which allows the user to modify the set of installed components or remove the
product completely. By default, this tool is given the name maintenancetool,
but this gives no indication of what the tool relates to. On some platforms,
the tool name can show up in desktop or application menus and the default name
can be confusing for users. Therefore, projects should provide a more specific
name, which can be done like so:
set(CPACK_IFW_PACKAGE_MAINTENANCE_TOOL_NAME
${PROJECT_NAME}_MaintenanceTool
)
set(CPACK_IFW_PACKAGE_MAINTENANCE_TOOL_INI_FILE
${CPACK_IFW_PACKAGE_MAINTENANCE_TOOL_NAME}.ini
)
include(CPackIFW)set(CPACK_IFW_PACKAGE_MAINTENANCE_TOOL_NAME
${PROJECT_NAME}_MaintenanceTool
)
set(CPACK_IFW_PACKAGE_MAINTENANCE_TOOL_INI_FILE
${CPACK_IFW_PACKAGE_MAINTENANCE_TOOL_NAME}.ini
)
include(CPackIFW)
.ini安装程序使用该文件来维护调用之间的状态信息。设置文件的名称.ini是可选的,但最好使名称与安装程序本身一致。通过上述设置,如果维护工具出现在用户的桌面或应用程序菜单中,用户将看到与项目相关的名称。
The .ini file is used by the installer to maintain state information between
invocations. Setting the name of the .ini file is optional, but making the
name consistent with the installer itself is preferable. With the above
settings, the user will see a name that relates to the project if the
maintenance tool shows up in their desktop or applications menu.
IFW 生成器的一个重要功能是它能够创建在线安装程序。部分或全部组件可以按需下载,而不是将它们捆绑为安装程序的一部分。如果一些可选组件很大,这尤其有利。在线安装程序的另一个好处是,如果在线存储库提供了更新版本,则可以升级各个组件,这提供了非常方便的升级路径。用户运行维护工具,该工具联系在线存储库集以确定可用组件及其版本。然后可以根据需要添加、删除或升级各个组件。
A significant feature of the IFW generator is its ability to create online installers. Some or all components can be downloaded on demand instead of bundling them as part of the installer. This is particularly advantageous if some optional components are large. An added benefit of an online installer is that individual components can be upgraded if newer versions are made available from the online repositories, which provides a very convenient upgrade path. Users run the maintenance tool which contacts the set of online repositories to determine the available components and their versions. Individual components can then be added, removed or upgraded as desired.
配置项目以支持可下载组件的第一步是指定安装程序从何处下载它们。使用通用命令指定主要默认存储库cpack_configure_downloads():
The first step in configuring a project to support downloadable components is
to specify where the installer will download them from. A primary default
repository is specified with the generic cpack_configure_downloads() command:
cpack_configure_downloads(baseUrl
[ALL]
[ADD_REMOVE | NO_ADD_REMOVE]
[UPLOAD_DIRECTORY dir]
)cpack_configure_downloads(baseUrl
[ALL]
[ADD_REMOVE | NO_ADD_REMOVE]
[UPLOAD_DIRECTORY dir]
)
安装程序将在其中baseUrl查找可下载组件的位置。安装程序将期望找到Updates.xml在该位置下调用的文件。如果ALL存在关键字,则所有组件都被视为可下载,无论它们是否明确标记为可下载。这是制作不带嵌入式软件包的完全在线安装程序的便捷方法,从而产生尽可能小的安装程序。
The baseUrl is the location where the installer will look for downloadable
components. The installer will expect to find a file called Updates.xml under
that location. If the ALL keyword is present, all components are treated as
downloadable regardless of whether they were explicitly marked as to be
downloadable or not. This is a convenient way of making a fully online
installer with no embedded packages, which yields the smallest possible
installer.
该ADD_REMOVE关键字指示安装程序使该程序包可供 Windows 的“添加/删除程序”功能使用,然后当用户选择通过 Windows 系统设置的该部分修改该程序包时,该功能将运行维护工具。关键字ALL暗示ADD_REMOVE,但给予
NO_ADD_REMOVE会覆盖该行为。
The ADD_REMOVE keyword directs the installer to make the package available to
Windows' Add/Remove Programs functionality, which will then run the maintenance
tool when the user elects to modify the package through that part of the
Windows system settings. The ALL keyword implies ADD_REMOVE, but giving
NO_ADD_REMOVE overrides that behavior.
该UPLOAD_DIRECTORY选项由支持可下载组件的其他 CPack 生成器类型使用(尽管这些生成器都没有主动维护),但 IFW 生成器会忽略它。运行时cpack,它会在单独的目录中创建可下载的包,以便可以将整个目录的内容上传到该baseUrl位置(必须手动完成)。该UPLOAD_DIRECTORY选项旨在允许项目覆盖此单独目录所在的位置,但 IFW 生成器始终会repository在基本目录下创建一个名为 located multilevels的目录_CPack_Packages。
The UPLOAD_DIRECTORY option is used by other CPack generator types that
support downloadable components (although none of those are actively
maintained), but it is ignored by the IFW generator. When cpack runs, it
creates downloadable packages in a separate directory so that the contents of
that whole directory can be uploaded to the baseUrl location (which must be
done manually). The UPLOAD_DIRECTORY option is intended to allow the project
to override where this separate directory is located, but the IFW generator
always creates a directory called repository located multiple levels deep
under the base _CPack_Packages directory.
IFW 生成器允许项目指定其他存储库以供维护工具和安装程序访问。如果不同的组件由不同的供应商提供,或者某些组件的发布计划与其他组件不同,则这会很有用。
The IFW generator allows projects to specify additional repositories for the maintenance tool and installer to access. This can be useful if different components are provided by different vendors or where some components have a different release schedule to others.
cpack_ifw_add_repository(repoName
URL baseUrl
[DISPLAY_NAME displayName]
[DISABLED]
[USERNAME username]
[PASSWORD password]
)cpack_ifw_add_repository(repoName
URL baseUrl
[DISPLAY_NAME displayName]
[DISABLED]
[USERNAME username]
[PASSWORD password]
)
是repoName一个内部跟踪名称,并且 的baseUrl含义与 类似cpack_configure_downloads()。该DISPLAY_NAME选项通常应该用于给出一个有意义的名称,否则baseUrl将显示为存储库名称,这往往不太用户友好。如果存储库需要用户名和密码,可以提供,但请记住,密码将以未加密的方式存储,并且应被视为不安全。该
DISABLED关键字指示默认情况下应禁用存储库,但用户可以在安装程序或维护工具的 UI 中启用它。
The repoName is an internal tracking name and the baseUrl has a similar
meaning as for cpack_configure_downloads(). The DISPLAY_NAME option should
generally be used to give a meaningful name, otherwise the baseUrl is shown
as the repository name, which tends to be less user friendly. If the repository
needs a user name and password, it can be supplied, but keep in mind that the
password will be stored unencrypted and should be considered insecure. The
DISABLED keyword indicates that the repository should be disabled by default,
but the user can enable it in the installer or maintenance tool’s UI.
发布包的主存储库和预览包的辅助存储库(默认情况下禁用)的示例可以如下配置:
An example of a main repository for release packages and a secondary repository for preview packages (disabled by default) could be configured like this:
include(CPack)
include(CPackIFW)
cpack_configure_downloads(
https://example.com/packages/product/release
ALL
)
cpack_ifw_add_repository(secondaryRepo
DISPLAY_NAME
"Preview features"
URL
https://example.com/packages/product/preview
DISABLED
)include(CPack)
include(CPackIFW)
cpack_configure_downloads(
https://example.com/packages/product/release
ALL
)
cpack_ifw_add_repository(secondaryRepo
DISPLAY_NAME
"Preview features"
URL
https://example.com/packages/product/preview
DISABLED
)
不幸的是,该cpack_configure_downloads()命令当前不支持指定显示名称,因此它提供的主 URL 将始终显示为裸 URL,而不是更用户友好的名称。
Unfortunately, the cpack_configure_downloads() command does not currently
support specifying a display name, so the main URL it supplies will always be
shown as a bare URL rather than a more user friendly name.
从 QtIFW 4.0 开始,生成的安装程序支持无人值守的命令行安装。项目方面不需要额外的配置,开发人员只需确保使用 QtIFW 4.0 或更高版本来生成安装程序即可。对于某些用户来说,在命令行上执行脚本安装的能力可能是一个重要功能,因此强烈建议使用 QtIFW 4.0 或更高版本(如果可能)。
Starting with QtIFW 4.0, the generated installers support unattended command-line installs. No extra configuration is needed on the part of the project, the developer just has to ensure that QtIFW 4.0 or later is used to generate the installer. The ability to perform scripted installs on the command line can be an important feature for some users, so it is highly recommended to use QtIFW 4.0 or later, if possible.
该包生成器的一个缺点是,与大多数其他生成器类型相比,生成的安装程序具有额外的开销。它包括安装程序界面、网络等所需的 Qt 支持。与其他生成器类型的几百 kB 相比,这甚至可以使一个简单的安装程序的大小达到 18 Mb 或更大。
One drawback of this package generator is that the installer produced has extra overhead compared to most other generator types. It includes the Qt support needed for the installer’s interface, networking and so on. This can make the size of even a trivial installer 18Mb or more, compared to a few hundred kB for other generator types.
上述讨论仅涵盖了 IFW 生成器的主要方面,还有更多可用功能,允许项目广泛定制安装程序和维护工具。对于许多项目来说,上述功能已经允许创建灵活、强大和跨平台的安装程序。如果需要进一步定制,所提供的功能将作为扩展的坚实基础。
The above discussion only covers the main aspects of the IFW generator, there are considerably more capabilities available which allow projects to customize the installer and maintenance tool extensively. For many projects, the above functionality already allows flexible, robust and cross-platform installers to be created. If further tailoring is needed, the features presented will serve as a solid base on which to extend.
WIX 软件包生成器使用WiX 工具集.msi生成 Windows 安装程序
。与 IFW 包生成器相比,它具有相似程度的 UI 可定制性,并具有以下优点:
The WIX package generator produces .msi installers for Windows using the
WiX toolset. Compared to the IFW package generator, it
has a similar degree of UI customizability and offers the following advantages:
msiexec。
msiexec tool.
另一方面,与IFW相比,它也有以下缺点:
On the other hand, it has the following disadvantages compared to IFW:
CPACK_WIX_COMPONENT_INSTALL和CPACK_COMPONENTS_GROUPING都被忽略(见下文)。
CPACK_WIX_COMPONENT_INSTALL and CPACK_COMPONENTS_GROUPING are both
ignored (see below).
默认情况下,WIX 生成器生成一个基于组件的包,该包将始终显示在 UI 中,就好像CPACK_COMPONENTS_GROUPING已设置为IGNORE. 如果不需要基于组件的包,
CPACK_MONOLITHIC_INSTALL可以将其设置为 true,但始终会安装所有定义的组件。不可能仅在整体安装程序中包含某些组件,如果CPACK_COMPONENTS_ALL设置了此选项,CMake 将发出警告并忽略CPACK_COMPONENTS_ALL.
By default, the WIX generator produces a component-based package which will
always be presented in the UI as though CPACK_COMPONENTS_GROUPING had been
set to IGNORE. If a component-based package is undesirable,
CPACK_MONOLITHIC_INSTALL can be set to true, but then all defined components
are always installed. It is not possible to only include some components in a
monolithic installer and if CPACK_COMPONENTS_ALL is set, CMake will issue a
warning and ignore CPACK_COMPONENTS_ALL.
WIX 安装程序的一个关键部分是它包含产品 GUID 和升级 GUID。如果任何其他已安装的软件包具有相同的升级 GUID,则该其他软件包将被升级,而不是将新软件包作为单独的产品安装。如果升级 GUID 相同但产品 GUID 不同,则升级被视为重大升级,新安装程序将完全替换旧软件包。如果产品 GUID 也相同,则只要安装程序报告比当前安装的软件包更新的版本号,新安装程序就应该能够执行次要升级。服务包是一个示例,其中维护相同的产品 GUID 作为它们所应用的基本版本。除非创建相当高级的安装程序或打包策略,否则项目通常需要在每个版本中更改产品 GUID,因为 Windows 本身对于将相同的产品 GUID 从一个包保留到另一个包的限制相当严格。
A key part of a WIX installer is that it contains a product GUID and an upgrade GUID. If any other installed package has the same upgrade GUID, that other package will be upgraded rather than installing the new package as a separate product. If the upgrade GUIDs are the same but the product GUIDs are different, then the upgrade is considered a major upgrade and the new installer will completely replace the old package. Where the product GUID is also the same, the new installer should be able to perform a minor upgrade as long as the installer reports a newer version number than the currently installed package. Service packs are an example where the same product GUID is maintained as the base version they apply to. Unless creating a fairly advanced installer or packaging strategy, projects will typically need to change the product GUID with each release, as the constraints from Windows itself for keeping the same product GUID from one package to another are fairly stringent.
CPack 提供对设置产品和升级 GUID 的支持。可以在调用之前设置 和 变量以
CPACK_WIX_PRODUCT_GUID手动控制它们,也可以不设置它们以允许在每次调用时生成新值。对于产品 GUID,这种自动生成可能是所需的行为,但理想情况下,升级 GUID 在产品的生命周期内应该永远不会改变。项目应该获取一个 GUID 并设置为该值,然后最好不要再更改它。这将确保所有未来版本都能够无缝升级旧版本。实际的 GUID 可以通过多种方式获取,例如命令行工具、基于 Web 的 UUID 生成器,甚至使用 CMake 本身使用命令。对于某些产品,升级 GUID 随着每个主要版本的变化而变化,以允许较旧的主要版本与较新的版本共存,从而方便用户的迁移路径,这可能是有意义的。CPACK_WIX_UPGRADE_GUIDinclude(CPack)cpackCPACK_WIX_UPGRADE_GUIDstring(UUID)
CPack provides support for setting the product and upgrade GUIDs. The
CPACK_WIX_PRODUCT_GUID and CPACK_WIX_UPGRADE_GUID variables can be
set before calling include(CPack) to control them manually, or they can be
left unset to allow cpack to generate new values each time it is invoked. For
the product GUID, this automatic generation is likely to be the desired
behavior, but the upgrade GUID should ideally never change for the life of the
product. Projects should obtain a GUID and set CPACK_WIX_UPGRADE_GUID to that
value, then ideally never change it again. This will ensure all future releases
are able to upgrade older releases seamlessly. The actual GUID can be obtained
by a variety of means such as command line tools, web-based UUID generators or
even with CMake itself using the string(UUID) command. For some products, it
may make sense for the upgrade GUID to change with each major release to allow
an older major release to co-exist with a newer one, thereby facilitating the
users’ migration path.
产品 GUID 必须更改的标准之一是文件名是否.msi更改。由于安装程序的文件名通常会包含一些版本详细信息,这意味着每个版本都将被视为主要升级。如果用户安装新版本,它将完全替换以前安装的任何版本。新版本可以安装到不同的目录,旧版本将被删除。然后使用默认安装目录(由
CPACK_PACKAGE_INSTALL_DIRECTORY,但用户可能更希望默认目录在升级过程中保持不变。理想情况下,默认目录应该仅在升级 GUID 更改时更改,因为这是提供从一个版本到另一版本的连续性的标识符。
One of the criteria around when a product GUID must change is if the name of
the .msi file changes. Since the installer’s file name would typically
include some version details, this means each release would be considered a
major upgrade. If the user installs the new version, it would completely
replace any previously installed version. The new version can be installed to a
different directory and the old one would be removed. It may be tempting to
then use a default installation directory (controlled by
CPACK_PACKAGE_INSTALL_DIRECTORY) that includes a version number, but users
would likely prefer the default directory to stay the same across upgrades. The
default directory should ideally only change if the upgrade GUID changes, since
that is the identifier that provides the continuity from one version to
another.
安装新软件包时,如果已安装具有相同升级 GUID 的另一个软件包,则会在版本之间进行检查。只有当新包是更高版本时才允许继续升级。此测试仅考虑前三个版本号组件,因此从升级角度来看,版本 2.7.4.3 和 2.7.4.9 将被视为同一版本。因此,打算使用 WIX 生成器的项目应避免使用超过三个版本号的组件。如果允许
CPACK_PACKAGE_VERSION从各个版本部分自动设置
CPACK_PACKAGE_VERSION_xxx,则这已经被强制执行。
When installing a new package and another package with the same upgrade GUID is
already installed, a check is made between the versions. Only if the new
package is of a later version will the upgrade be allowed to proceed. Only the
first three version number components are considered in this test, so versions
2.7.4.3 and 2.7.4.9 would be considered the same version from an upgrade
perspective. Projects intending to use the WIX generator should therefore avoid
using more than three version number components. If allowing
CPACK_PACKAGE_VERSION to be automatically set from the individual
CPACK_PACKAGE_VERSION_xxx version parts, this will already be enforced.
大多数 UI 默认值对于基本 WIX 包来说都是可以接受的。项目可能希望提供一个产品图标来代替通用 MSI 安装程序图标,以改进“添加/删除”区域中的品牌,但默认值通常是可以接受的。以下示例显示了 WIX 安装程序的基本配置。
Most of the UI defaults are acceptable for a basic WIX package. Projects may want to provide a product icon to use in place of the generic MSI installer icon for improved branding in the Add/Remove area, but the defaults are otherwise generally acceptable. The following example shows basic configuration of a WIX installer.
# Define generic setup for all generator types...
# WIX-specific configuration
set(CPACK_WIX_PRODUCT_ICON
${CMAKE_CURRENT_LIST_DIR}/Logo.ico
)
set(CPACK_WIX_UPGRADE_GUID
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
)
include(CPack)
# Define components and component groups...# Define generic setup for all generator types...
# WIX-specific configuration
set(CPACK_WIX_PRODUCT_ICON
${CMAKE_CURRENT_LIST_DIR}/Logo.ico
)
set(CPACK_WIX_UPGRADE_GUID
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
)
include(CPack)
# Define components and component groups...
NSIS 包生成器使用 Nullsoft Scriptable Install System为 Windows 生成安装程序可执行文件。它与 IFW 和 WIX 生成器具有许多相似的特征,包括一定程度的 UI 可定制性和对组件层次结构的支持。NSIS 生成器的优点包括:
The NSIS package generator produces installer executables for Windows using the Nullsoft Scriptable Install System. It shares a number of similar characteristics with the IFW and WIX generators, including a degree of UI customizability and support for component hierarchies. Advantages of the NSIS generator include:
NSIS 生成器有一些缺点:
The NSIS generator has a few drawbacks:
CPACK_NSIS_COMPONENT_INSTALL并且CPACK_COMPONENTS_GROUPING都被忽略。NSIS 生成器在这方面与 WIX 生成器具有相同的限制。
CPACK_NSIS_COMPONENT_INSTALL and CPACK_COMPONENTS_GROUPING are both
ignored. The NSIS generator has the same restrictions as the WIX generator in
this regard.
默认情况下,如果新软件包安装到与旧软件包相同的目录,这些安装程序将仅执行现有产品安装的升级。项目可以将该CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL
变量设置为 true,以强制安装程序首先检查注册表中是否存在软件包的现有安装。此检查不依赖于安装位置,因此它是检查现有安装是否需要升级的更可靠方法。因此,对于大多数项目,建议将此变量设置为 true。
By default, these installers will only perform an upgrade of an existing
product installation if the new package is installed to the same directory as the
old one. Projects can set the CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL
variable to true to force the installer to check the registry for an existing
installation of the package first. This check does not rely on the install
location, so it is a more reliable way to check for an existing installation to
be upgraded. As a result, setting this variable to true is recommended for most
projects.
NSIS 安装程序受益于覆盖许多区域的默认外观。应设置用于安装程序、卸载程序和产品本身的图标(如“添加/删除”区域中所示),因为默认图标要么质量低下,要么生成空白框。还应显式设置产品显示的名称,以避免 CPack 提供不适当的默认文本。以下示例显示了带有覆盖的基本配置,以避免大多数项目认为不合适的默认设置。
NSIS installers benefit from overriding the default appearance in a number of areas. The icons used for the installer, uninstaller and the product itself as shown in the Add/Remove area should be set, as the defaults are either of low quality or produce blank boxes. The name displayed for the product should also be explicitly set to avoid inappropriate default text supplied by CPack. The following example shows a basic configuration with overrides to avoid the defaults that most projects would find unsuitable.
# Define generic setup for all generator types...
# NSIS-specific configuration
set(CPACK_NSIS_MUI_ICON
${CMAKE_CURRENT_LIST_DIR}/InstallerIcon.ico ①
)
set(CPACK_NSIS_MUI_UNIICON
${CMAKE_CURRENT_LIST_DIR}/UninstallerIcon.ico ②
)
set(CPACK_NSIS_INSTALLED_ICON_NAME bin/MainApp.exe) ③
set(CPACK_NSIS_DISPLAY_NAME "My Project Suite") ④
set(CPACK_NSIS_PACKAGE_NAME "My Project") ⑤
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL YES)
include(CPack)
# Define components and component groups...# Define generic setup for all generator types...
# NSIS-specific configuration
set(CPACK_NSIS_MUI_ICON
${CMAKE_CURRENT_LIST_DIR}/InstallerIcon.ico ①
)
set(CPACK_NSIS_MUI_UNIICON
${CMAKE_CURRENT_LIST_DIR}/UninstallerIcon.ico ②
)
set(CPACK_NSIS_INSTALLED_ICON_NAME bin/MainApp.exe) ③
set(CPACK_NSIS_DISPLAY_NAME "My Project Suite") ④
set(CPACK_NSIS_PACKAGE_NAME "My Project") ⑤
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL YES)
include(CPack)
# Define components and component groups...
.ico) 或具有自己的嵌入式应用程序图标的可执行文件的路径。该路径应该是
相对于安装基点的安装位置。.ico) or an executable that has an
embedded application icon of its own. The path should be to the installed
location, relative to the base point of the install.Setup可能会附加到它后面。Setup may be appended to it in some
contexts.CMake 3.17 添加了更多自定义选项。默认的欢迎标题和结束标题可以分别使用变量
CPACK_NSIS_WELCOME_TITLE和来覆盖CPACK_NSIS_FINISH_TITLE。可以使用变量指定安装程序每页上显示的标题图像
CPACK_NSIS_MUI_HEADERIMAGE。CMake 3.20 添加了对CPACK_NSIS_BRANDING_TEXT和
CPACK_NSIS_BRANDING_TEXT_TRIM_POSITION变量的支持,可用于替换安装程序页面底部的通用Nullsoft 安装系统消息。
CMake 3.17 added a few more customization options.
The default welcome and finishing titles can be overridden using the variables
CPACK_NSIS_WELCOME_TITLE and CPACK_NSIS_FINISH_TITLE respectively.
The header image shown on each page of the installer can be specified with the
CPACK_NSIS_MUI_HEADERIMAGE variable.
CMake 3.20 added support for the CPACK_NSIS_BRANDING_TEXT and
CPACK_NSIS_BRANDING_TEXT_TRIM_POSITION variables, which can be used to
replace the generic Nullsoft Install System message along the bottom of the
installer pages.
CMake 3.17 提供的另一个自定义功能是能够更改卸载程序的通用默认名称。该CPACK_NSIS_UNINSTALL_NAME变量可用于指定不同的名称。这可以通过使其与包的关系更加明显来提高卸载程序的可用性。这使得卸载程序在正在运行的进程列表中或当用户在正在运行的应用程序之间切换时更容易识别。
Another customization made available by CMake 3.17 is the ability to change
the name of the uninstaller from its generic default.
The CPACK_NSIS_UNINSTALL_NAME variable can be used to specify a
different name.
This can improve the usability of the uninstaller by making its relationship
to the package more obvious.
This makes the uninstaller more identifiable in a list of running processes or
when the user is switching between running applications.
CMake 3.17 中还发生的一个重要变化是最低 NSIS 版本提升至 3.0(之前为 2.09)。CMake 3.17.0 版本未检查 NSIS 版本,如果 NSIS 版本不是 3.0 或更高版本,则会失败并出现 NSIS 解析错误。从 CMake 3.17.1 开始,会正确检查和报告更新的最低 NSIS 版本。
An important change that also occurred in CMake 3.17 was the minimum NSIS version was raised to 3.0 (previously it was 2.09). The CMake 3.17.0 release did not check the NSIS version and would fail with a NSIS parsing error if the NSIS version was not 3.0 or later. From CMake 3.17.1, the updated minimum NSIS version is properly checked and reported.
使用 CMake 3.18 或更高版本时,可以通过将该CPACK_NSIS_MANIFEST_DPI_AWARE变量设置为 true 来使安装程序支持 DPI。请注意,图标处理和某些 NSIS 插件可能无法在所有情况下产生可接受的结果,因此如果启用此选项,建议在不同的 DPI 设置下测试安装程序。
When CMake 3.18 or later is used, installers can be made DPI-aware by setting
the CPACK_NSIS_MANIFEST_DPI_AWARE variable to true.
Note that icon handling and some NSIS plugins might not yield acceptable
results in all cases, so it is advisable to test the installer at different DPI
settings if enabling this option.
在 Mac 上,产品通常以.dmg文件形式分发。它们就像磁盘映像一样,可以包含从单个应用程序到整套应用程序、文档链接等的任何内容。该区域的符号链接
/Applications经常作为图像的一部分提供,以便用户可以轻松地将应用程序拖到其上进行安装,因此这种生成器类型被称为 DragNDrop。特定于此生成器类型的配置变量DMG在其名称中使用,而不是DRAGNDROP,但请注意,它cpack
只会识别DragNDrop为生成器本身的名称。
On Mac, products are commonly distributed as a .dmg file. These act like a
disk image and can contain anything from a single application through to a
whole suite of applications, documentation links and so on. A symlink to the
/Applications area is frequently provided as part of the image so that users
can easily drag applications onto it to install them, hence the name DragNDrop
for this generator type. Configuration variables specific to this generator
type use DMG in their name rather than DRAGNDROP, but note that cpack
will only recognize DragNDrop as the name of the generator itself.
该.dmg格式更接近于存档而不是 UI 安装程序。组件用于控制是否.dmg创建一个或多个文件以及每个
.dmg文件包含的内容,但没有安装时 UI 来选择组件。用户需要打开.dmg文件并将内容拖到所需位置来安装它们。CPACK_COMPONENTS_ALL控制安装哪些组件,变量控制这些组件如何在文件CPACK_COMPONENTS_GROUPING之间分布,如下所示:.dmg
The .dmg format is closer to an archive than a UI installer. Components are
used to control whether one or multiple .dmg files are created and what each
.dmg file contains, but there is no install-time UI to choose components.
The user is expected to open the .dmg file(s) and drag the contents to the
desired location to install them. CPACK_COMPONENTS_ALL controls
which components are installed and the CPACK_COMPONENTS_GROUPING variable
controls how those components are distributed between .dmg file(s) as
follows:
ALL_COMPONENTS_IN_ONE
ALL_COMPONENTS_IN_ONE
.dmg
文件中。
.dmg
file.
ONE_PER_GROUP
ONE_PER_GROUP
.dmg文件中。
.dmg file.
IGNORE
IGNORE
.dmg文件中,并且所有组件组将被忽略。
.dmg file and all
component groups will be ignored.
使用 CMake 3.17 或更高版本时,如果需要.dmg,可以使用以下形式的变量指定
每个组件文件的文件名CPACK_DMG_<component>_FILE_NAME(仅与
ONE_PER_GROUP或IGNORE情况相关)。
When using CMake 3.17 or later, the file name for each component’s .dmg file
can be specified using variables of the form
CPACK_DMG_<component>_FILE_NAME if required (only relevant for the
ONE_PER_GROUP or IGNORE cases).
除了默认值之外,此包生成器类型通常需要很少的自定义。打开磁盘映像时显示的 Finder 窗口的大小和布局可以通过提供自定义.DS_Store文件来控制。该项目需要使用包含与最终磁盘映像相同内容的示例文件夹手动准备此类文件,或者可以在 AppleScript 中以编程方式创建该文件。该CPACK_DMG_DS_STORE变量可用于命名预先准备的
.DS_Store文件,也CPACK_DMG_DS_STORE_SETUP_SCRIPT可指向要在包生成时运行的 AppleScript 文件。对于任何一种情况,如果需要,都可以使用
CPACK_DMG_BACKGROUND_IMAGE变量设置背景图像,但将背景保留为空白默认值是相对常见的。对于磁盘映像不应提供
/Applications文件夹符号链接的情况,项目应设置
CPACK_DMG_DISABLE_APPLICATIONS_SYMLINK为 true。
This package generator type typically requires little customization beyond the
defaults.
The size and layout of the Finder window displayed when the disk image is
opened can be controlled by providing a custom .DS_Store file.
The project will need to either prepare such a file manually using an example
folder containing the same things as the final disk image, or it can be created
programmatically in AppleScript.
The CPACK_DMG_DS_STORE variable can be used to name a pre-prepared
.DS_Store file or CPACK_DMG_DS_STORE_SETUP_SCRIPT can point to an
AppleScript file to be run at package generation time.
For either case, a background image can be set with the
CPACK_DMG_BACKGROUND_IMAGE variable if desired, but leaving the
background at the blank default is relatively common.
For cases where the disk image should not provide a symlink to the
/Applications folder, the project should set
CPACK_DMG_DISABLE_APPLICATIONS_SYMLINK to true.
CPACK_PACKAGE_ICON
通过在格式中设置图标,可以为磁盘映像指定图标.icns。该图标仅用于表示.dmg
安装时的文件,而不用于表示.dmg文件本身。指定的图标可能会显示在 Finder 标题栏或某些 Finder 视图中,但它不是突出显示的图标。
An icon can be specified for the disk image by setting CPACK_PACKAGE_ICON
to an icon in .icns format. This icon is only used to represent the .dmg
file when mounted, not for the .dmg file itself. The specified icon may show
up in the Finder title bar or certain Finder views, but it is otherwise not a
prominently displayed icon.
有限的语言本地化是通过
CPACK_DMG_SLA_LANGUAGES和CPACK_DMG_SLA_DIR变量提供的。这些可用于提供在打开磁盘映像的许可协议阶段使用的特定短语,并提供许可协议的本地化版本。请参阅 DragNDrop 生成器的文档,了解如何使用这两个变量以及需要提供的语言文件的要求。
Limited language localization is provided through the
CPACK_DMG_SLA_LANGUAGES and CPACK_DMG_SLA_DIR variables.
These can be used to provide specific phrases used during the license agreement
phase of opening the disk image and to provide a localized version of the
license agreement.
See the DragNDrop generator’s documentation for how these two variables are
used and the requirements around the language files that need to be provided.
CMake 3.21 添加了指定用于图像的文件系统格式的功能
.dmg。该CPACK_DMG_FILESYSTEM变量可以设置为该
hdiutil -fs选项支持的任何值,但最典型的值是APFS或
HFS+。如果未设置此变量,则默认为HFS+。请注意,APFS 文件系统仅在 macOS 10.13 或更高版本上得到正式支持。
CMake 3.21 added the ability to specify the file system format used for the
.dmg image.
The CPACK_DMG_FILESYSTEM variable can be set to any value that the
hdiutil -fs option supports, but the most typical values are either APFS or
HFS+.
If this variable is not set, the default is HFS+.
Note that APFS file systems are only officially supported on macOS 10.13 or
later.
Bundle 生成器类型与 DragNDrop 生成器相关。它使用同一组DMG变量,加上一些它自己的变量。Bundle 生成器最初旨在生成单个应用程序包,并可能提交到 Apple App Store。如今,此类应用程序包在使用 CMake 的 Xcode 生成器构建过程中得到了更好的准备,因为这更符合 Apple 预期的流程。请参阅第 23 章,Apple 功能,了解准备此类应用程序包的推荐方法,而不是使用 CPack Bundle 生成器类型。
The Bundle generator type is related to the DragNDrop generator. It uses the
same set of DMG variables, plus some of its own. The Bundle generator was
originally intended for producing a single app bundle potentially for
submission to the Apple App Store. These days, such app bundles are better
prepared during the build itself using CMake’s Xcode generator, as this more
closely follows the process expected by Apple. See Chapter 23, Apple Features for the
recommended way of preparing such app bundles rather than using the CPack
Bundle generator type.
DragNDrop 生成器的替代方案是 Productbuild。它不生成.dmg磁盘映像,而是生成一个.pkg与 macOS 安装程序应用程序一起使用的包。CPACK_COMPONENTS_GROUPING被忽略,安装程序始终表现得好像该变量已设置为IGNORE。
CPACK_MONOLITHIC_INSTALL不应将此生成器设置为 true,因为这样做可能会产生损坏的安装程序。不支持安装程序类型,并且几乎没有自定义 UI 的能力,尽管默认值通常就足够了。
An alternative to the DragNDrop generator is productbuild. Instead of producing
a .dmg disk image, it produces a .pkg package for use with the macOS
Installer app. CPACK_COMPONENTS_GROUPING is ignored and the installer always
behaves as though this variable had been set to IGNORE.
CPACK_MONOLITHIC_INSTALL should not be set to true with this generator, as
doing so can produce broken installers. Installer types are not supported and
there is very little ability to customize the UI, although the defaults are
typically sufficient anyway.
与 IFW 生成器相比,productbuild 的主要优点是能够对安装程序进行签名。这可以通过设置
CPACK_PRODUCTBUILD_IDENTITY_NAME(如果
CPACK_PRODUCTBUILD_KEYCHAIN_PATH需要的话)签名详细信息来轻松配置。通常只需指定默认身份就足够了,可以像这样完成:
Compared to the IFW generator, the main advantage of productbuild is the
ability to sign the installer. This is easily configured by setting the
CPACK_PRODUCTBUILD_IDENTITY_NAME (and also
CPACK_PRODUCTBUILD_KEYCHAIN_PATH if required) to the signing details.
Often just specifying the default identity is enough, which can be done like
so:
set(CPACK_PRODUCTBUILD_IDENTITY_NAME
"Developer ID Installer"
)
include(CPack)set(CPACK_PRODUCTBUILD_IDENTITY_NAME
"Developer ID Installer"
)
include(CPack)
Productbuild 生成器缺乏对可下载组件的支持,因此无法创建在线安装程序。升级是通过替换现有安装的先前内容来处理的。与 NSIS 安装程序一样,如果不重新安装产品,则无法修改已安装组件集。通常也不可能将多个版本同时安装到不同的目录。
The productbuild generator lacks support for downloadable components, so the creation of online installers is not possible. Upgrades are handled by replacing the previous contents of an existing install. Like for NSIS installers, the set of installed components cannot be modified without reinstalling the product. It is also not typically possible to install multiple versions simultaneously to different directories.
默认情况下,productbuild 生成器生成的安装程序是可重定位的。这意味着,当程序包安装在最终用户的计算机上时,如果操作系统知道某个应用程序包与程序包提供的应用程序之一同名,则安装程序将覆盖该现有应用程序,无论该应用程序位于何处在文件系统上。在这些情况下,应用程序不会安装到默认
/Applications区域,这通常意味着它也不会显示在用户期望的位置。
Installers produced by the productbuild generator are relocatable by default.
What this means is that when the package is installed on an end user’s machine,
if the OS knows of an app bundle with the same name as one of the apps provided
by the package, the installer will overwrite that existing app no matter where
it is on the file system. The app will not be installed to the default
/Applications area in these cases, which usually means it also won’t show up
in places where the user expects it to.
对于在用来构建和测试包的机器上的开发人员来说,上述情况通常会出现。构建生成的应用程序包对于操作系统是已知的,因此在安装包时,构建树的应用程序包将用作该应用程序的安装位置,而不是/Applications. _CPack_Packages构建树的暂存目录中还会有该应用程序的另一个副本,它可以产生类似的行为。为了正确测试安装程序,在运行安装程序之前,需要先从开发人员的计算机中删除正在安装的应用程序包的所有副本。
The above situation commonly arises for developers on the machine they are
using to build and test packages.
The app bundle produced by the build is known to the OS, so when installing
the package, the build tree’s app bundle is used as the install location for
that app instead of the expected location in /Applications.
There will also be another copy of the app in the _CPack_Packages staging
directory of the build tree which can yield similar behavior.
To properly test the installer, all copies of the app bundles being installed
would need to be removed from the developer’s machine first before running the
installer.
上述重定位问题的一种解决方法是将组件标记为不可重定位。这会阻止安装程序选择现有应用程序包的位置。代价是,它也会阻止用户根据自己的意愿移动应用程序包。
One workaround to the above relocation problem is to mark components as not relocatable. This prevents the installer from selecting the location of an existing app bundle. The trade-off is that it also prevents the user from moving app bundles around should they so wish.
要使组件不可重定位,需要为每个组件提供自定义 plist 文件。这是通过使用PLIST以下cpack_add_component()
命令的选项来实现的:
To make a component non-relocatable, a custom plist file needs to be provided
for each component.
This is achieved by using the PLIST option with the cpack_add_component()
command:
cpack_add_component(MyProj_Runtime
... # Other options
PLIST runtime.plist
)cpack_add_component(MyProj_Runtime
... # Other options
PLIST runtime.plist
)
应通过使用命令--analyze的选项
来获取 plist 文件pkgbuild,然后根据需要更新该文件。项目命令的详细输出cpack也很有帮助:
The plist file should be obtained by using the --analyze option with the
pkgbuild command and then updating that file as needed.
The verbose output of a cpack command for the project can also be helpful:
cpack -G 产品构建 -V
cpack -G productbuild -V
典型的 plist 文件可能如下所示:
A typical plist file might look something like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>BundleHasStrictIdentifier</key>
<true/>
<key>BundleIsRelocatable</key>
<true/>
<key>BundleIsVersionChecked</key>
<true/>
<key>BundleOverwriteAction</key>
<string>upgrade</string>
<key>RootRelativeBundlePath</key>
<string>Applications/MyApp.app</string>
</dict>
</array>
</plist><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>BundleHasStrictIdentifier</key>
<true/>
<key>BundleIsRelocatable</key>
<true/>
<key>BundleIsVersionChecked</key>
<true/>
<key>BundleOverwriteAction</key>
<string>upgrade</string>
<key>RootRelativeBundlePath</key>
<string>Applications/MyApp.app</string>
</dict>
</array>
</plist>
BundleIsRelocatable将字典项更改为false以防止操作系统在安装时重新定位应用程序。<dict></dict>组件中的每个应用程序包都有一个部分。
Change the BundleIsRelocatable dictionary item to false to prevent the OS
from relocating the app on install. There will be one <dict></dict> section
for each app bundle in the component.
Productbuild 生成器应被视为旧版且不再受支持的 PackageMaker 生成器的替代品。Apple 不再提供 PackageMaker 应用程序,因此使用较新版本 macOS 的开发人员必须改用 Productbuild。自 CMake 3.17 起,PackageMaker 生成器已正式弃用。
The productbuild generator should be considered a replacement for the older and no longer supported PackageMaker generator. Apple no longer provides the PackageMaker app, so developers using newer versions of macOS must use productbuild instead. The PackageMaker generator is officially deprecated as of CMake 3.17.
在 Linux 系统上,RPM 是两种主要的包管理格式之一。RPM 包没有自己的 UI 功能,它们本质上只是具有相当广泛的元数据集和一些脚本功能的档案。系统的包管理器使用它们来管理包之间的依赖关系,向用户提供信息,触发安装前/安装后和卸载脚本等。
On Linux systems, RPM is one of the two dominant package management formats. RPM packages do not have UI features of their own, they are essentially just archives with a fairly extensive set of metadata and some scripting features. The system’s package manager uses these to manage dependencies between packages, provide information to the user, trigger pre/post install and uninstall scripts and so on.
由于包本身没有 UI 功能,因此该区域不需要进行自定义,但 RPM 生成器通过大量变量提供了元数据的广泛可自定义性。其中许多变量不需要显式设置,因为大多数默认值都适合不需要执行任何复杂操作的项目。对于不需要调用安装前/安装后或卸载脚本并且可以通过底层包创建工具自动确定包间依赖关系的包,定制量与其他包生成器类似。
Since the package itself has no UI features, there is no customization needed in that area, but the RPM generator provides extensive customizability of the metadata through a large number of variables. Many of these variables do not need to be explicitly set, since the majority of the defaults are appropriate for projects that don’t need to do anything complex. For packages that do not need to invoke pre/post install or uninstall scripts and for which inter-package dependencies can be automatically determined by the underlying package creation tool, the amount of customization is similar to that of other package generators.
RPM 生成器支持组件安装,但默认情况下禁用组件。当组件被禁用时,只会生成一个.rpm,并且行为就像CPACK_MONOLITHIC_INSTALL设置为 true 一样。在这种情况下,所有组件都包含在包装中。如果启用了组件,则CPACK_COMPONENTS_GROUPING具有其通常的含义,并且.rpm将创建多个文件。通过设置
CPACK_RPM_COMPONENT_INSTALL为 true 来启用组件,并像往常一样控制已安装的组件集CPACK_COMPONENTS_ALL。
The RPM generator supports component installs, but components are disabled by
default. When components are disabled, only a single .rpm is produced and the
behavior is as though CPACK_MONOLITHIC_INSTALL was set to true. All
components are included in the package in such cases. If components are
enabled, then CPACK_COMPONENTS_GROUPING has its usual meaning and
multiple .rpm files will be created. Components are enabled by setting
CPACK_RPM_COMPONENT_INSTALL to true and the set of installed components
is controlled by CPACK_COMPONENTS_ALL as usual.
# Define generic setup for all generator types...
set(CPACK_COMPONENTS_GROUPING ONE_PER_GROUP)
# RPM-specific configuration
set(CPACK_RPM_COMPONENT_INSTALL YES)
include(CPack)
# Define components and component groups...# Define generic setup for all generator types...
set(CPACK_COMPONENTS_GROUPING ONE_PER_GROUP)
# RPM-specific configuration
set(CPACK_RPM_COMPONENT_INSTALL YES)
include(CPack)
# Define components and component groups...
组件或组名称可能不适合用作包名称,这些名称通常作为文件.rpm名的一部分在 RPM 包管理器 UI 应用程序等中对用户可见。可以使用以下命令在每个组件的基础上设置这些名称CPACK_RPM_<COMP>_PACKAGE_NAME其中<COMP>是大写的组件名称。创建禁用组件的包时,可以通过设置来覆盖单个整体包名称
CPACK_RPM_PACKAGE_NAME。
The component or group names might not be suitable for use as package names,
which are typically visible to the user as part of the .rpm file name, within
RPM package manager UI applications, etc. These names can be set on a
per-component basis with CPACK_RPM_<COMP>_PACKAGE_NAME where <COMP> is
the uppercased component name. When creating a package with components
disabled, the single monolithic package name can be overridden by setting
CPACK_RPM_PACKAGE_NAME instead.
add_executable(sometool ...)
install(TARGETS sometool ... COMPONENT MyProjUtils)
set(CPACK_RPM_MYPROJUTILS_PACKAGE_NAME myproj-tools)
include(CPack)add_executable(sometool ...)
install(TARGETS sometool ... COMPONENT MyProjUtils)
set(CPACK_RPM_MYPROJUTILS_PACKAGE_NAME myproj-tools)
include(CPack)
文件的名称.rpm也可以自定义,项目很可能会想要这样做。每个组件的文件的名称.rpm由变量控制CPACK_RPM_<COMP>_FILE_NAME(或仅
CPACK_RPM_FILE_NAME用于非组件打包)。这些变量的默认值遵循以下模式:
The name of the .rpm files can also be customized and it is likely that
projects will want to do so. The name of each component’s .rpm file is
controlled by the CPACK_RPM_<COMP>_FILE_NAME variable (or just
CPACK_RPM_FILE_NAME for non-component packaging). The default value for
these variables follows this pattern:
<CPACK_PACKAGE_FILE_NAME>[-<component>].rpm<CPACK_PACKAGE_FILE_NAME>[-<component>].rpm
该<component>部分是原来的组件名称(即大小写没有变化)。此默认文件名的一个缺点是它不包含任何版本或体系结构详细信息,但通常需要(或至少需要)此类信息。通常最好指示cpack让底层包创建工具选择更好的默认包名称,这可以通过设置CPACK_RPM_<COMP>_FILE_NAME
特殊字符串来完成RPM-DEFAULT。下面给出了由这种安排产生的典型文件名的示例。
The <component> part is the original component name (i.e. no change in
upper/lowercase). One drawback to this default file name is that it does not
include any version or architecture details, but such information would
normally be required (or at least desirable). It is generally preferable to
instruct cpack to let the underlying package creation tool select a better
default package name, which can be done by setting CPACK_RPM_<COMP>_FILE_NAME
to the special string RPM-DEFAULT. Examples of typical file names produced by
this arrangement are given below.
包RPM-DEFAULT文件名将自动包含架构。如果需要显式指定架构,例如标记一个包以noarch表明它不是特定于架构的,则可以将每个组件CPACK_RPM_<COMP>_PACKAGE_ARCHITECTURE变量设置为所需的值,或者CPACK_RPM_PACKAGE_ARCHITECTURE如果没有特定于组件的覆盖,则可以将其设置为默认值已设置(它也用于整体包)。体系结构的默认值由 的
cpack输出计算得出uname -m,但如果在 64 位主机上构建 32 位包,则这将是错误的,因此项目需要显式设置体系结构值。
The RPM-DEFAULT package file name will automatically include the
architecture. If the architecture needs to be explicitly specified, such as to
mark a package as noarch to indicate it is not architecture specific, the
per-component CPACK_RPM_<COMP>_PACKAGE_ARCHITECTURE variable can be set
to the required value or CPACK_RPM_PACKAGE_ARCHITECTURE can be set to act
as the default if no component specific override is set (it is also used for
monolithic packages). The default value for the architecture is computed by
cpack as the output of uname -m, but if building a 32-bit package on a
64-bit host, this would be wrong and so the project would need to explicitly
set the architecture value.
RPM 文件需要有版本信息。默认情况下将使用 RPM 生成器
CPACK_PACKAGE_VERSION,但如果需要,也可以设置特定于 RPM 的版本号CPACK_RPM_PACKAGE_VERSION(但这种需要应该很少见)。请注意,目前无法指定每个组件的版本,CPack RPM 生成器当前仅限于对所有组件使用相同的版本。除了软件包版本之外,RPM 软件包还有一个单独的版本号,使用
CPACK_RPM_PACKAGE_RELEASE. 该版本号是软件包本身的版本,而不是产品的版本,因此如果版本号增加(例如,为了修复打包问题),软件包版本通常会保持不变。如果软件包版本发生更改,版本号通常会重置回 1,如果CPACK_RPM_PACKAGE_RELEASE未指定,则这是默认值。可选纪元也可以由 指定,
CPACK_RPM_PACKAGE_EPOCH并且它的使用在某些系统或存储库上可能比其他系统或存储库更常见。完整版本的格式E:X.Y.Z-R
为E纪元,并且必须是数字(如果提供)。当未设置纪元时,完整版本的格式为X.Y.Z-R。除非知道需要纪元值,否则项目通常不应设置纪元。
RPM files are required to have version information. The RPM generator will use
CPACK_PACKAGE_VERSION by default, but a RPM-specific version number can also
be set using CPACK_RPM_PACKAGE_VERSION if required (but the need for this
should be rare). Note that it is not currently possible to specify
per-component versions, the CPack RPM generator is currently limited to using
the same version for all components. In addition to the package version, RPM
packages also have a separate release number, which is specified using
CPACK_RPM_PACKAGE_RELEASE. This release number is the release of the
package itself, not of the product, so the package version would normally
remain constant if the release number is increased (e.g. to fix a packaging
issue). If the package version changes, the release number is usually reset
back to 1, which is the default value if CPACK_RPM_PACKAGE_RELEASE is not
specified. An optional epoch can also be specified by
CPACK_RPM_PACKAGE_EPOCH and its use may be more common on some systems or
repositories than others. The full version has the format E:X.Y.Z-R
where E is the epoch and must be a number if provided. When no epoch is set,
the full version has the format X.Y.Z-R. Unless it is known that an epoch
value is required, projects should generally leave the epoch unset.
除非项目显式覆盖CPACK_PACKAGE_VERSION和
CPACK_RPM_PACKAGE_ARCHITECTURE,否则它们的值在文件中将不可用
CMakeLists.txt,因为这些变量的默认值仅在cpack处理输入文件时计算,而不是在 CMake 运行时计算。这意味着直接稳健地设置包文件名比使用RPM-DEFAULT. 以下示例展示了如何使用该
RPM-DEFAULT功能:
Unless the project explicitly overrides CPACK_PACKAGE_VERSION and
CPACK_RPM_PACKAGE_ARCHITECTURE, their values won’t be available within
CMakeLists.txt files because the defaults for these variables are only
computed when cpack processes the input file, not when CMake runs. This means
it is a lot more work to robustly set the package file name directly rather
than using RPM-DEFAULT. The following example shows how to make use of the
RPM-DEFAULT feature:
# Optional, default of 1 is often okay
set(CPACK_RPM_PACKAGE_RELEASE 5)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(CPACK_RPM_PACKAGE_ARCHITECTURE i686)
endif()
set(CPACK_RPM_MYPROJUTILS_PACKAGE_NAME myproj-tools)
set(CPACK_RPM_MYPROJUTILS_FILE_NAME RPM-DEFAULT)
include(CPack)# Optional, default of 1 is often okay
set(CPACK_RPM_PACKAGE_RELEASE 5)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(CPACK_RPM_PACKAGE_ARCHITECTURE i686)
endif()
set(CPACK_RPM_MYPROJUTILS_PACKAGE_NAME myproj-tools)
set(CPACK_RPM_MYPROJUTILS_FILE_NAME RPM-DEFAULT)
include(CPack)
对于上述情况,假设CPACK_PACKAGE_VERSION计算结果为 形式的字符串X.Y.Z,该示例通常会生成如下包文件名:
For the above, assuming CPACK_PACKAGE_VERSION evaluates to a string of the
form X.Y.Z, the example would typically lead to package file names like:
myproj-tools-XYZ-5.i686.rpm myproj-tools-XYZ-5.x86_64.rpm
myproj-tools-X.Y.Z-5.i686.rpm myproj-tools-X.Y.Z-5.x86_64.rpm
正如前一章所讨论的,默认的基本安装点在 Linux 系统上不太可能是理想的,这延伸到了 RPM 包的创建。事实上,对于除 Windows 之外的所有系统,通常也应该通过显式设置变量来为打包设置更合适的基点
CPACK_PACKAGING_INSTALL_PREFIX。扩展上一章的示例,该项目可能需要执行如下操作:
As discussed in the previous chapter, the default base install point is
unlikely to be desirable on Linux systems and this extends to the creation of
RPM packages. In fact, for all but Windows systems, a more appropriate base
point should generally be set for packaging too by explicitly setting the
CPACK_PACKAGING_INSTALL_PREFIX variable. Extending the example from the
previous chapter, the project may want to do something like the following:
if(NOT WIN32 AND
CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(CMAKE_INSTALL_PREFIX
"/opt/mycompany.com/${PROJECT_NAME}"
)
set(CPACK_PACKAGING_INSTALL_PREFIX
${CMAKE_INSTALL_PREFIX}
)
endif()if(NOT WIN32 AND
CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(CMAKE_INSTALL_PREFIX
"/opt/mycompany.com/${PROJECT_NAME}"
)
set(CPACK_PACKAGING_INSTALL_PREFIX
${CMAKE_INSTALL_PREFIX}
)
endif()
RPM 包的一个独特功能是它们可以包含重定位路径。包可以指定一个或多个路径前缀,然后用户可以选择在安装时将其重新定位到其文件系统的另一部分。为了支持此功能,该CPACK_RPM_PACKAGE_RELOCATABLE变量必须设置为 true,然后CPACK_RPM_RELOCATION_PATHS可以包含允许用户重新定位的路径前缀列表。如果使用此功能,开发人员应查阅 RPM 生成器的文档,以了解如何处理相对路径以及适用于这两个变量的各种默认回退。另请注意,如果该项目作为 Linux 发行版的一部分包含在内,则发行版维护者可能需要覆盖安装前缀变量和重定位目录,因此更愿意保持简单。
A feature unique to RPM packages is that they can include relocation paths.
Packages can specify one or more path prefixes which the user can then choose
to relocate to another part of their file system at install time. To support
this feature, the CPACK_RPM_PACKAGE_RELOCATABLE variable must be set to
true and then CPACK_RPM_RELOCATION_PATHS can contain a list of path
prefixes that the user will be allowed to relocate. If using this feature,
developers should consult the RPM generator’s documentation to understand how
relative paths are treated and the various default fall backs that apply to both
of these variables. Note also that if the project is included as part of a
Linux distribution, the distribution maintainers will likely need to override
both the install prefix variables and the relocation directories, so prefer to
keep things simple.
RPM 包创建工具通常会在将可执行文件和共享库添加到包之前删除所有调试符号。基本原理是应该最小化发布二进制文件的大小,并且它们通常会隐藏实现细节并且不提供调试设施。通常,剥离由变量控制CPACK_STRIP_FILES
,该变量决定打包期间是否作为分阶段安装的一部分执行剥离,但对于 RPM 生成器,RPM 包创建工具通常默认执行其自己的剥离。因此,即使 为CPACK_STRIP_FILESfalse 或未设置,剥离仍然可能发生。根本问题是包创建工具rpmbuild通常有一个暂存安装部分,该部分在创建最终.rpm包之前剥离二进制文件并执行其他任务。传统上,提供的解决方法cpack是通过设置
CPACK_RPM_SPEC_INSTALL_POST变量来覆盖该行为,通常设置为类似
/bin/true. 该方法已被弃用,改为使用
CPACK_RPM_SPEC_MORE_DEFINE:
The RPM package creation tool would normally be expected to strip executables
and shared libraries of all debug symbols before adding them to the package.
The rationale is that the size of release binaries should be minimized and they
would normally hide implementation details and not provide debugging
facilities. Normally, stripping is controlled by the CPACK_STRIP_FILES
variable, which determines whether or not stripping is performed as part of the
staged install during packaging, but in the case of the RPM generator, the RPM
package creation tool often performs its own stripping by default. Therefore,
even if CPACK_STRIP_FILES is false or unset, stripping may still occur. The
underlying problem is that the package creation tool rpmbuild typically has a
post staging install section which strips binaries and performs other tasks
before creating the final .rpm package. Traditionally, the workaround offered
by cpack is to override that behavior by setting the
CPACK_RPM_SPEC_INSTALL_POST variable, usually to something like
/bin/true. That approach is deprecated in favor of using
CPACK_RPM_SPEC_MORE_DEFINE instead:
# Prevent stripping and other post-install steps during
# package creation
set(CPACK_RPM_SPEC_MORE_DEFINE
"%define __spec_install_post /bin/true"
)# Prevent stripping and other post-install steps during
# package creation
set(CPACK_RPM_SPEC_MORE_DEFINE
"%define __spec_install_post /bin/true"
)
虽然上述防止剥离的技术有效,但它也放弃了通常应用的所有其他操作(例如,Python 文件的自动字节码编译、特定于体系结构的后处理)。一个可能更好的替代方案是允许剥离 中的二进制文件
.rpm并生成单独的 debuginfo 包。CMake 3.7 中添加了对生成 debuginfo 包的初始支持,并在 3.8 和 3.9 中得到了进一步改进。要启用此功能,通常需要将其中一个
CPACK_RPM_DEBUGINFO_PACKAGE或特定于组件的等效项
设置CPACK_RPM_<COMP>_DEBUGINFO_PACKAGE为 true。生成的 debuginfo 包将包含源文件以及调试信息。源是从默认情况下获取的CMAKE_SOURCE_DIR,CMAKE_BINARY_DIR但如果需要,可以用变量覆盖CPACK_BUILD_SOURCE_DIRS。可以使用
CPACK_RPM_DEBUGINFO_EXCLUDE_DIRS和
CPACK_RPM_DEBUGINFO_EXCLUDE_DIRS_ADDITION变量排除源目录层次结构的一部分,尽管项目可能只想设置后者。前者通常用于排除系统目录并具有适当的默认值。发行版维护者可能希望CPACK_RPM_DEBUGINFO_EXCLUDE_DIRS
独立于项目在 中设置的内容
进行覆盖CPACK_RPM_DEBUGINFO_EXCLUDE_DIRS_ADDITION,因此使用两个单独的变量。
While the above technique for preventing stripping works, it also discards all
the other operations that would normally be applied (e.g. automatic byte code
compilation for python files, architecture-specific post processing). A
potentially better alternative is to allow stripping of the binaries in the
.rpm and produce a separate debuginfo package. Initial support for producing
debuginfo packages was added in CMake 3.7 and was further improved in 3.8 and
3.9. To enable this feature, all that is usually required is to set either
CPACK_RPM_DEBUGINFO_PACKAGE or the component-specific equivalent
CPACK_RPM_<COMP>_DEBUGINFO_PACKAGE to true. The debuginfo packages
produced will contain source files as well as the debug information. The
sources are taken from CMAKE_SOURCE_DIR and CMAKE_BINARY_DIR by default,
but this can be overridden with the CPACK_BUILD_SOURCE_DIRS variable if
required. Parts of the source directory hierarchy can be excluded using the
CPACK_RPM_DEBUGINFO_EXCLUDE_DIRS and
CPACK_RPM_DEBUGINFO_EXCLUDE_DIRS_ADDITION variables, although projects
probably only want to set the latter. The former is typically used to exclude
system directories and has an appropriate default value. Distribution
maintainers may want to override CPACK_RPM_DEBUGINFO_EXCLUDE_DIRS
independently of what the project would set in
CPACK_RPM_DEBUGINFO_EXCLUDE_DIRS_ADDITION, hence the use of two separate
variables.
在生成debuginfo包时,有时可能会遇到如下错误:
When producing debuginfo packages, errors like the following may sometimes be encountered:
CPackRPM:源目录路径“/path/to/source/dir”更短 比 debuginfo 源目录路径 '/usr/src/debug/SomeProject-XYZ-Linux/src_0'!源目录 路径必须比 debuginfo 源目录路径长。放 CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX 变量更短 值或使源目录路径更长。需要 调试信息包装。请参阅文档 CPACK_RPM_DEBUGINFO_PACKAGE 变量了解详细信息。
CPackRPM: source dir path '/path/to/source/dir' is shorter than debuginfo sources dir path '/usr/src/debug/SomeProject-X.Y.Z-Linux/src_0'! Source dir path must be longer than debuginfo sources dir path. Set CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX variable to a shorter value or make source dir path longer. Required for debuginfo packaging. See documentation of CPACK_RPM_DEBUGINFO_PACKAGE variable for details.
由于作为 debuginfo 处理的一部分重写路径的方式,源树的路径需要比源的预期安装位置长。请注意,这可能会影响源树位置通常是固定的持续集成系统。这种对较长路径长度的需求可能与可能需要最小化路径长度的其他约束相冲突,因此请仔细考虑此类约束是否适用于该项目。
Due to the way paths are rewritten as part of the debuginfo processing, the path to the source tree needs to be longer than the intended installed location of the sources. Note that this may impact continuous integration systems where the location of the source tree is typically fixed. This need for a longer path length may be in conflict with other constraints where the path length may need to be minimized, so consider carefully whether such constraints may apply to the project.
源 RPM 也可以由 RPM 生成器生成。它们与 debuginfo 包类似,但仅包含源代码,不包含调试信息。它们的生成方式与其他包生成器的源包相同,并且 RPM 生成器的文档包括显示如何从源 RPM 构建二进制 RPM 的基本说明,这可能是一个有用的验证步骤。
Source RPMs can also be produced by the RPM generator. These are similar to the debuginfo packages but only contain the sources and no debugging information. They are produced in the same way as source packages for other package generators and the RPM generator’s documentation includes basic instructions showing how to build a binary RPM from the source RPM, which may be a useful verification step.
# 创建源RPM
cpack -G RPM --config CPackSourceConfig.cmake
# 验证是否可以从中生成二进制 RPM
mkdir -p build_dir/BUILD \
构建目录/BUILDROOT \
build_dir/RPMS \
build_dir/来源\
build_dir/规格\
构建目录/SRPMS
rpmbuild --define "_topdir build_dir" \
--rebuild <源 RPM 文件名># Create source RPM
cpack -G RPM --config CPackSourceConfig.cmake
# Verify that a binary RPM can be produced from it
mkdir -p build_dir/BUILD \
build_dir/BUILDROOT \
build_dir/RPMS \
build_dir/SOURCES \
build_dir/SPECS \
build_dir/SRPMS
rpmbuild --define "_topdir build_dir" \
--rebuild <source-RPM-filename>RPM 生成器支持的变量比上面讨论的变量多得多。可以指定有关包提供或需要什么的详细信息,或者可以指示包创建工具自动计算它们。如果该包替换或与其他包冲突,也可以指定。可以给出在包安装和卸载之前或之后运行的脚本,或者如果需要完全控制,项目可以提供自己的自定义.spec文件模板,而不是使用提供的默认模板
cpack(尽管如果可能的话应该避免这种情况,因为它否定了)已经提供了大部分功能cpack。
The RPM generator supports many more variables than the ones discussed
above. Details about what the packages provide or require can be specified or
the package creation tool can be directed to automatically compute them. If the
package replaces or conflicts with other packages, this can also be specified.
Scripts to be run before or after package installation and uninstallation can
be given, or if complete control is needed the project can provide its own
custom .spec file template instead of using the default one provided by
cpack (although this should be avoided if possible, since it negates much of
the functionality already provided by cpack).
DEB 格式是 Linux 系统的另一种主要软件包格式,DEB 和 RPM 具有许多相似的特征。DEB 包基本上也只是带有相关元数据的档案,系统的包管理器使用这些元数据来强制依赖关系、触发脚本等。
The DEB format is the other dominant package format for Linux systems and both DEB and RPM share many similar characteristics. DEB packages are also basically just archives with associated metadata, which the system’s package manager uses to enforce dependencies, trigger scripts and so on.
DEB 和 RPM 之间的一个区别是 DEB 包的准备不需要特殊的工具,而 RPM 包则需要特殊的工具。这允许在本身不使用 DEB 格式的系统上创建 DEB 包,这意味着可以在基于 RPM 的系统(例如 RedHat、SuSE 等)上生成 RPM 和 DEB 包。主要需要注意的是在非DEB系统上创建DEB包时,诸如此类的工具dpkg-shlibdeps不可用,因此无法计算自动依赖关系之类的东西。
One difference between DEB and RPM is that the preparation of DEB packages does
not require a special tool, unlike RPM packages which do. This allows DEB
packages to be created on systems that do not themselves use the DEB format,
which means it is possible to produce both RPM and DEB packages on RPM-based
systems such as RedHat, SuSE, etc. The main caveat to this is that when
creating DEB packages on non-DEB systems, tools such as dpkg-shlibdeps are
not available, so things like automatic dependencies cannot be computed.
组件的处理方式与 RPM 非常相似,并且具有类似的配置变量。通过设置为 true 来启用组件
CPACK_DEB_COMPONENT_INSTALL(此变量不遵循用于所有其他 DEB 特定变量的命名,这些变量的名称前缀为
CPACK_DEBIAN_而不是CPACK_DEB_)。包名有类似的
CPACK_DEBIAN_PACKAGE_NAME和变量,而文件名则由和
CPACK_DEBIAN_<COMP>_PACKAGE_NAME
控制。与 RPM 一样,DEB 也存在相同的文件命名问题,只是应使用
特殊值而不是. 如果提供任何其他值,文件名必须以
或结尾。DEB 版本控制的处理方式也与 RPM 非常相似,就像指定架构一样。提供了等效的 DEB 变量,并
替换了变量名称。CPACK_DEBIAN_FILE_NAMECPACK_DEBIAN_<COMP>_FILE_NAMEDEB-DEFAULTRPM-DEFAULT.deb.ipkDEBIANRPM
Components are handled in a very similar way to RPM and have analogous
configuration variables. Components are enabled by setting
CPACK_DEB_COMPONENT_INSTALL to true (this variable does not follow the
naming used for all other DEB-specific variables, which have a name prefixed by
CPACK_DEBIAN_ rather than CPACK_DEB_). Package names have analogous
CPACK_DEBIAN_PACKAGE_NAME and CPACK_DEBIAN_<COMP>_PACKAGE_NAME
variables, while file names are controlled by CPACK_DEBIAN_FILE_NAME and
CPACK_DEBIAN_<COMP>_FILE_NAME. The same file naming issues apply to DEB
as for RPM, except the special value DEB-DEFAULT should be used instead of
RPM-DEFAULT. If providing any other value, the file name must end in .deb
or .ipk. Versioning for DEB is also handled in a very similar way to RPM, as
is specifying an architecture. Equivalent DEB variables are provided, with
DEBIAN replacing RPM in the variable names.
与 RPM 相比,DEB 包生成器影响依赖关系处理方式的变量较少。如果在可用该工具的基于 DEB 的主机上执行打包dpkg-shlibdeps,则可以通过将CPACK_DEBIAN_PACKAGE_SHLIBDEPS
组件特定CPACK_DEBIAN_<COMP>_PACKAGE_SHLIBDEPS变量设置为 true 来自动计算共享库依赖项。CPACK_DEBIAN_PACKAGE_DEPENDS手动指定的依赖关系可以通过和
变量提供
CPACK_DEBIAN_<COMP>_PACKAGE_DEPENDS,如果同时使用手动和自动依赖关系,则手动指定的依赖关系将与自动确定的依赖关系合并。但请注意,如果设置了特定于组件的依赖变量,则非组件变量不会用于该组件。如果启用自动依赖关系计算,它将填充组件特定的变量,因此如果项目仅设置CPACK_DEBIAN_PACKAGE_DEPENDS,则对于填充自动依赖关系的那些组件,它将被忽略。因此,始终填充可能
CPACK_DEBIAN_<COMP>_PACKAGE_DEPENDS比
CPACK_DEBIAN_PACKAGE_DEPENDS启用自动依赖项时更稳健。如果通过选项
CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS指定组件间依赖关系,则项目还应设置为 true ,这将在生成的组件包中强制执行这些依赖关系。DEPENDScpack_add_component()
The DEB package generator has fewer variables to influence how dependencies are
handled compared to RPM. If packaging is being performed on a DEB-based host
where the dpkg-shlibdeps tool is available, the shared library dependencies
can be automatically computed by setting CPACK_DEBIAN_PACKAGE_SHLIBDEPS
or the component specific CPACK_DEBIAN_<COMP>_PACKAGE_SHLIBDEPS variables
to true. Manually specified dependencies can be provided through the
CPACK_DEBIAN_PACKAGE_DEPENDS and
CPACK_DEBIAN_<COMP>_PACKAGE_DEPENDS variables and will be merged with the
automatically determined ones if both manual and automatic dependencies are
used. Note, however, that if a component-specific dependency variable is set,
the non-component variable is not used for that component. If automatic
dependency computation is enabled, it populates the component-specific
variables, so if the project sets only CPACK_DEBIAN_PACKAGE_DEPENDS, it will
be ignored for those components where automatic dependencies are populated.
Therefore, it may be more robust to always populate
CPACK_DEBIAN_<COMP>_PACKAGE_DEPENDS rather than
CPACK_DEBIAN_PACKAGE_DEPENDS when automatic dependencies are enabled.
Projects should also set CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS to true if
inter-component dependencies are specified via the DEPENDS option to
cpack_add_component(), which will then enforce those dependencies in the
generated component packages.
与上面相关的是,每个包还可以指定它需要的共享库。在提供该readelf工具的平台上,可以通过设置为 true 自动确定这些库依赖项
CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS。然后,该readelf工具用于确定每个共享对象所需的共享库,并将该信息添加到包中。该
变量控制是强制执行CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY精确 ( =) 还是最小 ( ) 要求。>=
Related to the above, each package can also specify the shared libraries it
requires. On platforms that provide the readelf tool, these library
dependencies can be determined automatically by setting
CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS to true. The readelf tool is then
used to determine the shared libraries each shared object needs and that
information is added to the package. The
CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY variable controls whether
exact (=) or minimum (>=) requirements are enforced.
DEB 生成器的文档详细介绍了上面未提及的许多其他特定于 DEB 的变量。特别是,一些变量可用于指定包需要、提供、替换等内容。还可以设置一些特定于 DEB 的元数据项,例如维护者详细信息、包组或类别等。开发人员应查阅 DEB 生成器的文档以获取完整的受支持变量集。
The DEB generator’s documentation details a number of other DEB-specific variables not mentioned above. In particular, some variables can be used to specify what the package(s) require, provide, replace and so on. Some DEB-specific metadata items can also be set, such as maintainer details, package group or category, etc. Developers should consult the DEB generator’s documentation for the full set of supported variables.
CMake 3.10 中添加了 FreeBSD 包生成器。它不支持组件并且始终生成单个.pkg文件。可以设置一些特定于 FreeBSD 的变量来指定基本的包元数据,其中一些可以回退到 DEB 或 RPM 特定的变量。许多包配置可以由通用CPACK_…
变量而不是生成器特定变量指定,因此该生成器的配置可以相当基本。建议项目开发人员查阅 FreeBSD 生成器的文档以了解可用的功能和限制。
The FreeBSD package generator was added in CMake 3.10.
It does not support components and always produces a single .pkg file.
Some FreeBSD-specific variables can be set to specify basic package metadata,
with a few falling back to DEB or RPM specific variables.
Much of the package configuration can be specified by the generic CPACK_…
variables rather than generator specific variables, so configuration of this
generator can be fairly basic.
Project developers are advised to consult the FreeBSD generator’s documentation
for available features and limitations.
Cygwin 是一个更基本的包生成器。它本质上只是 BZip2 存档的包装器,除了通用变量之外几乎不提供任何配置。项目可能希望考虑使用一种简单的存档格式。
An even more basic package generator is that for Cygwin. It is essentially just a wrapper around a BZip2 archive and offers next to no configuration beyond the generic variables. Projects may wish to consider using one of the simple archive formats instead.
NuGet 生成器支持的选项遵循与已经讨论的其他生成器类似的模式。CMake 3.12 首次提供了对 NuGet 包格式的支持。CMake 3.20 进一步扩展了可用配置变量集,提供了对包图标、许可证文件和区域设置的特定于生成器的控制。有关支持选项的完整列表,请参阅生成器的文档。
The options supported by the NuGet generator follow a similar pattern to the other generators already discussed. Support for the NuGet package format first became available with CMake 3.12. CMake 3.20 expanded the set of available configuration variables further, providing generator-specific control of package icons, license files and the locale. See the generator’s documentation for the full list of supported options.
外部生成器是在 CMake 3.13 中添加的,与所有其他包生成器有很大不同。这个特定的生成器会写出一个包含包元数据、组件详细信息和其他 CPack 信息的 JSON 文件,但它本身并不生成包。如果需要,它可以安装通常打包到临时暂存区域的文件。其他工具预计会使用 JSON 文件和分阶段安装区域来使用自己的方法生成包。该生成器的主要目标是以平台和发行版可以在自己的政策和技术限制内轻松使用的方式呈现 CPack 积累的详细信息。系统不必将整个包创建过程委托给 CPack,而是可以读取 CPack 提供的描述,并可以选择使用分阶段安装区域,并将它们提供给自己的现有方法。因此,该生成器的受众相当狭窄,并且只可能与发行版维护人员、系统集成商等相关。感兴趣的读者应查阅最新的 CMake 文档,以获取 JSON 格式和支持的自定义选项的详细说明。
The External generator was added in CMake 3.13 and is very different to all the other package generators. This particular generator writes out a JSON file containing package metadata, component details and other CPack information, but it does not produce a package itself. It can install the files that would normally be packaged into a temporary staging area if requested. Other tools are expected to consume the JSON file and staged install area to produce packages using their own methods. The main goal of this generator is to present the details accumulated by CPack in a way that platforms and distributions can easily use within their own policy and technical constraints. Instead of having to delegate the entire package creation process to CPack, systems can read the description CPack provides and optionally use the staged install area and feed these into their own existing methods. As such, this generator has a fairly narrow audience and is only likely to be relevant for distribution maintainers, system integrators, etc. The interested reader should consult the latest CMake documentation for a detailed description of the JSON format and supported customization options.
关于打包的首要决定之一是项目将为其版本提供哪些包格式。一个好的起点是考虑提供至少一种简单的存档格式,然后为每个目标平台提供一种本机格式。当最终用户想要同时安装产品的多个版本时,存档格式很方便,因为他们可以将发布存档解压到不同的目录。只要包是完全可重定位的,这就是一个简单而有效的策略。为了获得最广泛的兼容性,建议在 Windows 上使用 ZIP 存档,在基于 Unix 的系统上使用 TGZ。
One of the first decisions to be made regarding packaging is which package formats the project will provide for its releases. A good starting point is to consider providing at least one simple archive format and then one native format for each target platform. The archive format is convenient when end users want to install multiple versions of the product simultaneously, since they can then just unpack the release archives to different directories. As long as the packages are fully relocatable, this is a simple and effective strategy. For the broadest compatibility, ZIP archives are recommended for Windows and TGZ for Unix-based systems.
根据目标平台的不同,适用不同的非存档格式。如果 UI 安装程序适用于所有平台,请考虑使用 IFW 生成器来获得一致的最终用户体验,无论平台如何。这些安装程序还提供了最大的可定制性、本地化和可下载组件的选项。如果首选更多本机安装程序,那么选择将取决于项目认为更重要的内容。对于 Windows,WIX 或 NSIS 可能都适用,并且功能非常相似。对于 Mac,多组件项目可能更喜欢使用 Productbuild 生成器来获得更简洁的安装体验,但 DragNDrop 生成器更有可能受到非组件项目的最终用户的青睐,因为它提供了更大的简单性和更大的灵活性。在 Linux 上,如果不使用 IFW 生成器来实现跨平台一致性,请考虑提供 RPM 和 DEB 软件包以供最终用户最广泛采用。
Different non-archive formats are appropriate depending on the target platform. If a UI installer is appropriate for all platforms, then consider using the IFW generator for a consistent end user experience regardless of platform. These installers also offer the greatest customizability, localization and options for downloadable components. If more native installers are preferred, then the choices will depend on what the project considers more important. For Windows, either WIX or NSIS may be appropriate and the capabilities are fairly similar. For Mac, a multi component project may prefer the productbuild generator for a cleaner installation experience, but the DragNDrop generator is more likely to be preferred by end users for non-component projects since it offers greater simplicity and more flexibility. On Linux, consider providing both RPM and DEB packages for the broadest adoption by end users if not using the IFW generator for cross-platform consistency.
特别考虑最终用户是否能够在无头系统上安装产品。这直接影响包格式的选择以及组件需要定义和打包的方式。对于无头系统,必须提供非 UI 安装方法,并且包不应需要 UI 相关的依赖项。这意味着 UI 组件需要与非 UI 组件分开。这对于 RPM 和 DEB 包格式尤其重要,其中包间依赖关系通常由包管理器强制执行,因此需要 UI 依赖关系的组件包可能会为无头系统引入大量不需要的 UI 相关包。
Give particular consideration to whether end users should be able to install the product on a headless system. This directly impacts both the choice of package formats and the way components need to be defined and packaged. For a headless system, a non-UI installation method must be available and packages should not require UI-related dependencies. This means UI components need to be separated out from non-UI components. This is especially important for RPM and DEB package formats where inter-package dependencies are typically enforced by the package manager, so a component package that requires UI dependencies would potentially pull in a large number of unwanted UI-related packages for a headless system.
定义组件名称时,请考虑到项目可能用作某些较大项目层次结构的子项目。在组件名称中包含项目名称,以防止项目之间的名称冲突。UI 安装程序中向用户显示的组件名称、包文件名称等可以设置为不同的名称,而不是依赖于 CMake 项目内部使用的组件名称。事实上,鼓励为组件设置自定义显示名称和描述,包括在包格式支持的情况下提供本地化值。
When defining component names, allow for the possibility that the project may be used as a child of some larger project hierarchy. Include the project name in the component name to prevent name clashes between projects. The component names shown to users in UI installers, package file names, etc. can be set to something different rather than relying on the component name used internally within the CMake project. In fact, setting custom display names and descriptions for components is encouraged, including providing localized values where the package format supports it.
设置组件详细信息时,优先使用相关 CMake 模块定义的命令,而不是直接设置变量。cpack_add_component()、等命令
cpack_add_component_group()使用命名参数,这使得设置各种选项非常可读且更易于维护。它们也更加健壮,因为参数名称中的任何错误都会被命令捕获,而如果变量名称拼写错误,直接设置变量将不会被注意到。
When setting component details, prefer to use the commands defined by the
relevant CMake modules rather than setting variables directly. Commands such as
cpack_add_component(), cpack_add_component_group(), etc. use named
arguments which make setting various options very readable and easier to
maintain. They are also more robust, since any error in argument names will be
caught by the command, whereas setting variables directly will silently go
unnoticed if variable names are misspelled.
在配置各种生成器的详细信息时,潜在的大量变量可能会影响内容的打包方式。在许多情况下,默认值是可以接受的,但一些细节应始终由项目设置。项目应显式设置所有三个CPACK_PACKAGE_VERSION_MAJOR、
CPACK_PACKAGE_VERSION_MINOR和CPACK_PACKAGE_VERSION_PATCH变量,因为默认版本详细信息很少合适或可能并不总是可靠。还应始终设置包名称、描述和供应商详细信息。为了确保生成的输入文件中变量值的稳健转义,请始终显式设置CPACK_VERBATIM_VARIABLES为 true。
When configuring details for the various generators, a potentially large number
of variables can influence the way contents are packaged. In many cases, the
defaults are acceptable, but some details should always be set by the project.
Projects should explicitly set all three of the CPACK_PACKAGE_VERSION_MAJOR,
CPACK_PACKAGE_VERSION_MINOR and CPACK_PACKAGE_VERSION_PATCH variables,
since the default version details are rarely suitable or might not always be
reliable. The package name, description and vendor details should also always
be set. To ensure robust escaping of variable values in generated input files,
always explicitly set CPACK_VERBATIM_VARIABLES to true.
在大多数情况下,项目希望避免在默认安装目录的名称中包含版本号。许多安装程序支持就地更新现有安装,因此目录名称中的任何版本号在产品升级后都将不合适。用户可能还希望目录名称在升级过程中保持不变,以便他们可以编写跨版本工作的包装器脚本、启动器等。简单归档包是例外,这就是为什么非组件归档生成的默认行为主要遵循将提取的内容放置在包含包名称和版本的适当命名的子目录下的常见约定。对于基于组件的包,项目将希望设置CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY为 true 以获得类似的行为。
In most cases, projects will want to avoid including a version number in the
name of the default installation directory. A number of installers support
updating an existing install in-place, so any version number in the directory
name will be inappropriate after a product upgrade. Users may also prefer the
directory name to stay the same across upgrades so that they can write wrapper
scripts, launchers, etc. that work across versions. Simple archive packages are
the exception to this, which is why the default behavior for non-component
archive generation mostly follows the common convention of placing extracted
contents under an appropriately named subdirectory that includes both the
package name and the version. For component based packages, projects will want
to set CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY to true to get similar
behavior.
RPM 和 DEB 包应该优先将包文件名分别设置为RPM-DEFAULT
和DEB-DEFAULT。这确保了包文件名遵循通用命名约定,并且也是将包版本和体系结构详细信息合并到包文件名中的一种更简单的方法。不要依赖 CPack 提供的默认 RPM 或 DEB 包文件名,因为它们省略了版本和体系结构详细信息。
RPM and DEB packages should prefer to set package file names to RPM-DEFAULT
and DEB-DEFAULT respectively. This ensures that package file names follow the
common naming conventions and it is also a much simpler way of incorporating
the package version and architecture details into the package file names. Do
not rely on the default RPM or DEB package file names provided by CPack, since
they omit the version and architecture details.
如果在使用 RPM 生成器时应保留发布包的调试信息,请考虑使用 debuginfo 功能,而不是阻止包创建的剥离步骤。防止剥离需要禁用包生成的其他可能需要的方面,并且需要将调试详细信息作为发布包的一部分公开。debuginfo 功能允许提供适当的发布包,并在单独的包中捕获调试详细信息,该包可以分发给最终用户,也可以不分发给最终用户。DEB 生成器还支持在使用 CMake 3.13 或更高版本时创建 debuginfo 包。
If debug information should be retained for release packages when using the RPM generator, consider using the debuginfo functionality rather than preventing the stripping step of package creation. Preventing stripping requires disabling other potentially desirable aspects of package generation and requires exposing debug details as part of the release package. The debuginfo functionality allows a proper release package to be provided, with debugging details captured in a separate package that can be distributed or not to end users. The DEB generator also supports creating debuginfo packages when using CMake 3.13 or later.
如果需要通过单次调用生成多架构包cpack
,请使用该CPACK_INSTALL_CMAKE_PROJECTS变量合并来自多个构建树的组件。无论使用什么 CMake 版本或 CMake 生成器,此方法也可用于创建调试和发布包。如果将 CMake 3.16 或更高版本与多配置生成器一起使用,生成多配置包的更简单方法是直接调用该cpack工具并使用命令行选项指定要打包的配置集-C。无论使用哪种方法,始终将发布组件列在最后,以防调试和发布配置将工件安装到相同的文件名和目录中。理想情况下,无论如何都不应发生这种情况,但对于这样做可能有意义的情况,发布工件可能是首选。
If multi-architecture packages need to be produced from a single cpack
invocation, use the CPACK_INSTALL_CMAKE_PROJECTS variable to incorporate
components from multiple build trees.
This method can also be used to create debug-and-release packages regardless of
the CMake version or CMake generator used.
If using CMake 3.16 or later with a multi-config generator, a simpler way to
produce a multi-config package is to invoke the cpack tool directly and
specify the set of configurations to be packaged with the -C command line
option.
With either method, always list the release components last in case both
debug and release configurations install artifacts to the same file name and
directory.
Ideally this should not occur anyway, but for cases where it may make sense to
do so, the release artifact is likely to be the preferred one.
探索并了解项目将支持的每个 UI 安装程序提供的 UI 自定义选项。强烈建议定义适当的产品图标,以确保专业的外观和感觉。项目还应始终提供自己的自述文件、欢迎信息和许可证详细信息,以便任何安装程序或包的元数据都不会使用 CPack 提供的占位符文本。
Explore and understand the UI customization options provided by each UI installer that the project will support. Defining appropriate product icons is highly recommended to ensure a professional look and feel. Projects should also always provide their own readme, welcome and license details so that the placeholder text provided by CPack is not used by any of the installers or packages’ metadata.
对于任何复杂程度适中的项目,它很可能会依赖一个或多个外部依赖项。这些可以是常用的工具包,例如 zlib、OpenSSL、Boost 等,也可以是同一组织的私有项目或用作资源、测试数据等的内容。在某些情况下,项目可以期望操作系统提供所有必需的依赖项。例如,如果项目作为该操作系统的一部分进行分发,那么这将是合适的。对于独立项目,项目更有可能控制其依赖项的确切版本,以确保构建可重复并且发布包具有已知的来源。当构建与可能具有不同依赖性要求的其他项目共享的持续集成系统时,这一点尤其重要。
For any project of modest complexity, it is likely that it will rely on one or more external dependencies. These could be commonly available toolkits such as zlib, OpenSSL, Boost, etc., private projects by the same organization or content to be used as resources, test data and so on. In some situations, the project can expect the operating system to supply all required dependencies. This would be appropriate if the project is being distributed as part of that operating system, for example. For standalone projects, it is more likely that the project should be in control of the exact version of its dependencies to ensure that builds are repeatable and that release packages have known origins. This is especially important when building on continuous integration systems being shared with other projects that might have different dependency requirements.
CMake 提供了一些关于如何将外部内容引入构建的选择。在相当原始的级别,该file(DOWNLOAD)命令可用于检索特定文件,无论是在配置阶段还是作为在脚本模式下处理 CMake 文件的一部分(即cmake -P)。虽然这有其用途,但通常远远达不到整合整个项目所需的功能水平。为了下载和构建整个依赖项,CMake 中的传统方法是使用模块ExternalProject。它长期以来一直是 CMake 的一部分,除了简单的下载和构建之外,还有多种用途。CMake 3.11 中添加的模块FetchContent构建于基础之上ExternalProject,并开辟了各种新用例,包括处理项目之间共享的依赖项以及在一次构建中支持整个项目层次结构。该ExternalData模块提供了另一种在构建时处理外部内容的替代方案,重点关注测试用例的数据。
CMake provides a few choices for how to bring external content into a build. At
a fairly raw level, the file(DOWNLOAD) command can be used to retrieve a
specific file, either during the configure stage or as part of processing a
CMake file in script mode (i.e. cmake -P). While this has its uses, it is
usually well short of the level of functionality needed to incorporate whole
projects. For downloading and building an entire dependency, the traditional
approach in CMake has been to use the ExternalProject module. This has been a
part of CMake for a long time and has a variety of uses apart from simply doing
a download and build. The FetchContent module added in CMake 3.11 is built on
top of ExternalProject and opens up a variety of new use cases, including
handling dependencies shared between projects and supporting entire project
hierarchies in one build. The ExternalData module offers another alternative
for handling external content at build time, with a focus on data for test
cases.
该ExternalProject模块的主要目的是允许下载和构建无法轻松直接成为主项目一部分的外部项目。外部项目作为其自己的单独子构建添加,与主项目有效隔离,并或多或少被视为黑匣子。这意味着它可以用于构建不同架构、不同构建设置的项目,甚至可以使用 CMake 以外的构建系统构建项目。它还可用于处理定义目标的项目或安装与主项目冲突的组件。
The ExternalProject module’s main purpose is to enable downloading and
building external projects that cannot be easily made part of the main project
directly. The external project is added as its own separate child build,
effectively isolated from the main project and treated more or less as a black
box. This means it can be used to build projects for a different architecture,
different build settings or even to build a project with a build system other
than CMake. It can also be used to handle a project that defines targets or
install components that clash with those of the main project.
ExternalProject通过在主项目中定义一组构建目标来工作,这些目标代表获取和构建外部项目的不同阶段。然后将它们收集到代表整个序列的单个 CMake 目标下。时间戳用于跟踪哪些阶段已经执行,除非相关细节发生变化,否则不需要重复。默认的阶段集如下:
ExternalProject works by defining a set of build targets in the main project
that represent the distinct stages of obtaining and building the external
project. These are then collected under a single CMake target which represents
the whole sequence. Timestamps are used to keep track of which stages have
already been performed and do not need to be repeated unless relevant details
change. The default set of stages are as follows:
cmake在下载的源上执行。一些信息是从主构建传递的,以使配置外部 CMake 项目相当无缝。对于非 CMake 外部项目,可以提供自定义命令来运行等效步骤,例如使用configure适当的选项运行脚本。
cmake on the downloaded source. Some information is passed through
from the main build to make configuring external CMake projects fairly
seamless. For non-CMake external projects, a custom command can be provided to
run the equivalent steps, such as running a configure script with appropriate
options.
ExternalProject模块提供了是否运行测试阶段(默认情况下不运行)以及是否应该在安装阶段之前或之后进行的灵活性。如果启用测试阶段,test则将假定外部项目中存在默认目标,但可以指定自定义命令以提供对测试阶段所做操作的完全控制。
ExternalProject module provides
flexibility in whether or not to run a test stage (by default it doesn’t) and
whether it should come before or after the install stage. If the test stage is
enabled, a default test target will be assumed to exist in the external
project, but custom commands can be specified to provide full control over what
the test stage does.
该模块允许定义其他自定义阶段并将其插入到上述工作流程中的任何点,但默认的阶段集通常足以满足大多数项目的需要。默认阶段的详细信息均由模块提供的主函数设置ExternalProject_Add()。该函数接受许多选项,所有这些选项都在模块的文档中详细说明。下面列出了一些更常用的选项和一些典型场景,以帮助指导读者如何充分利用这些
ExternalProject功能。
The module allows other custom stages to be defined and inserted into any point
in the above workflow, but the default set of stages are typically sufficient
for most projects. The details for the default stages are all set by the main
function provided by the module, ExternalProject_Add(). This function
accepts many options, all of which are detailed in the module’s documentation.
A selection of the more commonly used ones and some typical scenarios are given
below to help guide the reader on how to make the most of what
ExternalProject offers.
最简单的情况是从 URL 下载源存档并将其构建为 CMake 项目。实现此目的所需的最少信息就是URL,其提供方式如下:
The simplest case involves downloading a source archive from a URL and building
it as a CMake project. The minimal information needed to achieve this is just
the URL, which is provided like so:
include(ExternalProject)
ExternalProject_Add(SomeExtProj
URL http://example.com/releases/myproj_1.2.3.tar.gz
)include(ExternalProject)
ExternalProject_Add(SomeExtProj
URL http://example.com/releases/myproj_1.2.3.tar.gz
)
该函数的第一个参数始终是要在主项目中创建的构建目标的名称。该目标将用于引用外部项目的整个构建过程。默认情况下,它被添加到主项目的
all目标中,但是可以通过添加常用选项来禁用它,这与、等EXCLUDE_FROM_ALL
命令具有相同的效果。
在上面的示例中,构建目标将导致在主项目的构建阶段执行以下操作:add_executable()add_custom_target()SomeExtProj
The first argument to the function is always the name of a build target to be
created in the main project. This target will be used to refer to the external
project’s whole build process. By default, it is added to the main project’s
all target, but this can be disabled by adding the usual EXCLUDE_FROM_ALL
option, which has the same effect as it does for commands like
add_executable(), add_custom_target(), etc.
In the above example, building the SomeExtProj target will result in the
following being performed during the build stage of the main project:
cmake使用基于主构建的默认选项运行。
cmake with default options based on the main build.
install目标。
install target.
这些步骤都使用在构建目录中创建的一组单独的目录来保存源、构建输出、时间戳以及与外部项目的构建相关的其他临时文件。这些目录的结构取决于几个不同的因素,模块文档提供了如何选择目录结构的详细说明。一个更简单的起点是展示主项目如何控制位置而不是依赖默认值。可以使用该PREFIX选项设置目录的基本位置。
These steps all use a separate set of directories created in the build
directory to hold the sources, build outputs, timestamps and other temporary
files associated with the external project’s build. The structure of these
directories depends on a few different factors and the module documentation
provides a detailed explanation of how the directory structure is chosen. A
simpler starting point is to show how the main project can control the
locations rather than relying on the defaults. The base location of the
directories can be set using the PREFIX option.
ExternalProject_Add(SomeExtProj
PREFIX prefixDir
URL http://example.com/releases/myproj_1.2.3.tar.gz
)ExternalProject_Add(SomeExtProj
PREFIX prefixDir
URL http://example.com/releases/myproj_1.2.3.tar.gz
)
当以这种方式使用时,目录布局将基于prefixDir,通常应作为绝对路径提供,并且通常位于主项目构建区域内的某个位置。在此位置下创建的默认相对目录布局如下所示。解压后的存档将位于其中prefixDir/src/SomeExtProj,并且 CMake 构建将用作
prefixDir/src/SomeExtProj-build其构建目录。
When used this way, the directory layout will be based under prefixDir, which
should generally be provided as an absolute path and would normally be
somewhere within the main project’s build area. The default relative directory
layout created under this location is shown below. The unpacked archive will be
in prefixDir/src/SomeExtProj and the CMake build will use
prefixDir/src/SomeExtProj-build as its build directory.
可以设置EP_PREFIX和EP_BASE目录属性来影响上述布局,ExternalProject有关详细信息,请参阅文档。前缀和这些目录属性仅提供对目录结构的粗略控制。对于需要的情况,ExternalProject_Add()允许直接设置部分或全部单独目录:
The EP_PREFIX and EP_BASE directory properties can be set to influence the
above layout, see the ExternalProject documentation for details. The prefix
and these directory properties only provide coarse control over the directory
structure. For those cases where it is needed, ExternalProject_Add() allows
some or all of the individual directories to be set directly:
ExternalProject_Add(SomeExtProj
DOWNLOAD_DIR downloadDir
SOURCE_DIR sourceDir
BINARY_DIR binaryDir
INSTALL_DIR installDir
TMP_DIR tmpDir
STAMP_DIR stampDir
URL http://example.com/releases/myproj_1.2.3.tar.gz
)ExternalProject_Add(SomeExtProj
DOWNLOAD_DIR downloadDir
SOURCE_DIR sourceDir
BINARY_DIR binaryDir
INSTALL_DIR installDir
TMP_DIR tmpDir
STAMP_DIR stampDir
URL http://example.com/releases/myproj_1.2.3.tar.gz
)
在实践中,TMP_DIR和STAMP_DIR很少被使用,但其他的与主要项目有更直接的相关性,有时会提供。默认安装位置将取决于外部项目,通常是系统范围的位置,因此INSTALL_DIR指定它是很常见的,以便于将外部项目的所有最终工件收集到构建目录中的一个位置(进一步的步骤需要使外部项目使用指定的INSTALL_DIR,如后面的示例所示)。
In practice, the TMP_DIR and STAMP_DIR would rarely be used, but the others
are of more direct relevance to the main project and are sometimes provided.
The default install location will be up to the external project, which will
typically be a system wide location, so it is very common for INSTALL_DIR to
be specified to facilitate collecting all the final artifacts of external
projects in one place within the build directory (further steps are required to
make the external projects use the specified INSTALL_DIR, as later examples
will show).
另一种有用的技术是提供SOURCE_DIR并给出已填充的现有目录的位置。当以这种方式使用时,不需要给出下载方法,在这种情况下,该命令将简单地使用指定源目录的现有内容。这是为不同平台构建主项目源代码树的一部分的非常方便的方法。例如:
Another useful technique is to provide SOURCE_DIR and give a location of an
existing directory that has already been populated. When used this way, no
download method needs to be given, in which case the command will simply use
the existing contents of the specified source directory. This can be a very
convenient way of building a part of the main project’s source tree for a
different platform. For example:
ExternalProject_Add(Firmware
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/Firmware
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/Firmware-artifacts
#... other options to configure differently
)ExternalProject_Add(Firmware
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/Firmware
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/Firmware-artifacts
#... other options to configure differently
)
当外部项目也使用 CMake 作为其构建系统时,可能需要添加cmake命令行选项来影响其配置。实现此目的的最直接方法是使用选项CMAKE_ARGS,该选项后面应该跟有要传递给外部项目命令的参数
cmake。上面的示例可以扩展为使用工具链文件、配置发布版本并使用指定的安装目录,如下所示:
When the external project also uses CMake as its build system, it can be
desirable to add cmake command line options to influence its configuration.
The most direct way to achieve this is using the CMAKE_ARGS option, which
should be followed by the arguments to be passed to the external project’s
cmake command. The above example can be extended to use a toolchain file,
configure a release build and use the nominated install directory like so:
set(toolchainFile
${CMAKE_CURRENT_LIST_DIR}/fwtoolchain.cmake
)
ExternalProject_Add(Firmware
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/Firmware
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/Firmware-artifacts
CMAKE_ARGS
-D CMAKE_TOOLCHAIN_FILE=${toolchainFile}
-D CMAKE_BUILD_TYPE=Release
-D CMAKE_INSTALL_PREFIX=<INSTALL_DIR> # See below
)set(toolchainFile
${CMAKE_CURRENT_LIST_DIR}/fwtoolchain.cmake
)
ExternalProject_Add(Firmware
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/Firmware
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/Firmware-artifacts
CMAKE_ARGS
-D CMAKE_TOOLCHAIN_FILE=${toolchainFile}
-D CMAKE_BUILD_TYPE=Release
-D CMAKE_INSTALL_PREFIX=<INSTALL_DIR> # See below
)
如果需要设置多个 CMake 选项,则生成的cmake命令行的长度可能会成为问题。另一种方法是指定要使用定义的缓存变量,CMAKE_CACHE_ARGS而不是通过 定义它们CMAKE_ARGS。这些参数应采用 形式
-Dvariable:TYPE=value,并将转换为包含 形式命令的文件set(variable value CACHE TYPE "" FORCE)。cmake然后该文件会通过一个选项传递到命令行-C。cmake效果与通过选项直接在命令行上设置变量相同-D。还有其他选项可用于更改 CMake 生成器以及 CMake 调用方式的其他一些不太常见的方面,但这些选项不太常用。有关更多详细信息,请参阅模块文档。
If more than a couple of CMake options need to be set, the length of the
generated cmake command line could become a problem. An alternative is to
specify cache variables to be defined using CMAKE_CACHE_ARGS rather than
defining them via CMAKE_ARGS. These arguments are expected to be in the form
-Dvariable:TYPE=value and will be converted to a file containing commands of
the form set(variable value CACHE TYPE "" FORCE). This file is then passed to
the cmake command line with a -C option. The effect is the same as if the
variables had been set directly on the cmake command line via -D options.
There are other options which can be used to change the CMake generator and a
few other less common aspects of how CMake is invoked, but these are less
frequently used. Consult the module documentation for further details.
如果外部项目不使用 CMake 作为其构建系统,则
CONFIGURE_COMMAND可以提供选项来提供要执行的替代自定义命令,而不是运行cmake. 例如,许多项目提供了一个配置脚本,可以像这样设置:
If the external project does not use CMake as its build system, the
CONFIGURE_COMMAND option can be given to provide an alternative custom
command to be executed instead of running cmake. For example, many projects
provide a configure script, which could be set up like so:
ExternalProject_Add(SomeAutotoolsProj
URL someUrl
CONFIGURE_COMMAND <SOURCE_DIR>/configure
...
)ExternalProject_Add(SomeAutotoolsProj
URL someUrl
CONFIGURE_COMMAND <SOURCE_DIR>/configure
...
)
配置命令在构建目录中运行,但configure脚本将在源目录中。上面演示了另一种策略,即使用默认结构,但命令的
占位符支持提供源目录的位置,而不是显式定义用于外部项目的目录布局。前面的示例还使用了安装目录的占位符作为 的值传递CMAKE_INSTALL_PREFIX。占位符只是用尖括号括起来的特定目录的选项名称,最常用的是<SOURCE_DIR>,<BINARY_DIR>和<INSTALL_DIR>。<DOWNLOAD_DIR>也可用于 CMake 3.11 或更高版本。占位符的完整列表在模块文档中给出。
The configure command is run in the build directory, but the configure script
will be in the source directory. Rather than explicitly having to define the
directory layout to be used for the external project, the above demonstrates an
alternative strategy whereby the default structure is used, but the command’s
placeholder support provides the location of the source directory. The
previous example also used a placeholder for the install directory passed as
the value for CMAKE_INSTALL_PREFIX. A placeholder is just the option name for
a particular directory surrounded by angle brackets, the most commonly used
being <SOURCE_DIR>, <BINARY_DIR> and <INSTALL_DIR>. <DOWNLOAD_DIR> is
also available with CMake 3.11 or later. The full list of placeholders is given
in the module documentation.
如果CONFIGURE_COMMAND未使用该选项,则假定该项目是 CMake 构建,并且外部项目的构建步骤将使用与主项目相同的构建工具。对于这种情况,构建步骤的默认行为是合适的,不需要特殊处理。当CONFIGURE_COMMAND提供时,默认构建工具被假定为make,默认构建命令是在没有任何明确目标的情况下调用make。如果应构建非默认目标或make需要其他构建工具,则必须提供自定义构建命令。例如:
If the CONFIGURE_COMMAND option is not used, the project is assumed to be a
CMake build and the external project’s build step will use the same build tool
as the main project. For such cases, the default behavior of the build step is
suitable and no special handling is needed. When CONFIGURE_COMMAND is
provided, the default build tool is assumed to be make and the default build
command is to invoke make without any explicit target. If a non-default
target should be built instead or a build tool other than make is needed, a
custom build command must be provided. For example:
find_program(MAKE_EXECUTABLE NAMES nmake gmake make)
ExternalProject_Add(SomeAutotoolsProj
URL someUrl
CONFIGURE_COMMAND <SOURCE_DIR>/configure
BUILD_COMMAND ${MAKE_EXECUTABLE} specialTool
)find_program(MAKE_EXECUTABLE NAMES nmake gmake make)
ExternalProject_Add(SomeAutotoolsProj
URL someUrl
CONFIGURE_COMMAND <SOURCE_DIR>/configure
BUILD_COMMAND ${MAKE_EXECUTABLE} specialTool
)
自定义构建命令可以执行任何操作,它不必是已知的构建工具。它甚至可以设置为空字符串以有效绕过构建阶段。可以预见的是,安装阶段也会延续相同的模式。对于 CMake 项目,将调用主项目的构建工具来构建install默认调用的目标,而对于非 CMake 项目,默认命令只是make install. 该INSTALL_COMMAND选项可用于提供自定义安装命令,也可以将其设置为空字符串以完全禁用安装阶段。当主项目可以使用构建阶段的结果而无需任何进一步安装时,通常会使用此方法。
The custom build command could do anything, it doesn’t have to be a known build
tool. It can even be set to an empty string to effectively bypass the build
stage. Predictably, the same pattern continues for the install stage too.
For CMake projects, the main project’s build tool will be invoked to build a
target called install by default, whereas for non-CMake projects the default
command is simply make install. The INSTALL_COMMAND option can be used to
provide a custom install command or it can be set to an empty string to disable
the install stage altogether. This is often used when the main project can use
the results of the build stage without needing any further install.
ExternalProject_Add(SomeAutotoolsProj
URL someUrl
CONFIGURE_COMMAND <SOURCE_DIR>/configure
BUILD_COMMAND ${MAKE_EXECUTABLE} specialTool
INSTALL_COMMAND "" # Disables the install stage
)ExternalProject_Add(SomeAutotoolsProj
URL someUrl
CONFIGURE_COMMAND <SOURCE_DIR>/configure
BUILD_COMMAND ${MAKE_EXECUTABLE} specialTool
INSTALL_COMMAND "" # Disables the install stage
)
应注意正确处理安装阶段。如果外部项目使用 CMake 作为其构建系统,则默认安装规则的目标由CMAKE_INSTALL_PREFIX缓存变量控制。如果未设置此变量,则将使用默认位置,这通常会导致外部项目安装到系统范围的位置,这通常不是期望的结果(如果项目是在持续集成系统中构建的话,当然不会) )。同样,如果外部项目使用 CMake 以外的构建系统,则默认安装命令将为make
install,它可能会再次尝试安装到系统范围的位置。对于 CMake 情况,通过CMAKE_ARGS如前面的示例所示设置缓存变量可以解决这种情况,而对于基于 Makefile 的项目,类似以下内容通常是合适的:
Care should be taken to handle the install stage properly. If the external
project uses CMake as its build system, the destination of the default install
rule is controlled by the CMAKE_INSTALL_PREFIX cache variable. If this
variable is not set, the default location will be used, which typically results
in the external project being installed to a system wide location, which is not
usually the desired outcome (certainly not if the project is being built within
a continuous integration system). Similarly, if the external project uses a
build system other than CMake, the default install command will be make
install, which again will likely try to install to a system wide location. For
the CMake case, setting the cache variable via CMAKE_ARGS as shown in the
earlier example addresses the situation, while for a Makefile based project,
something like the following is usually appropriate:
ExternalProject_Add(otherProj
URL ...
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/otherProj-install
CONFIGURE_COMMAND
<SOURCE_DIR>/configure
INSTALL_COMMAND
${MAKE_EXECUTABLE} DESTDIR=<INSTALL_DIR> install
)ExternalProject_Add(otherProj
URL ...
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/otherProj-install
CONFIGURE_COMMAND
<SOURCE_DIR>/configure
INSTALL_COMMAND
${MAKE_EXECUTABLE} DESTDIR=<INSTALL_DIR> install
)
该INSTALL_DIR选项除了定义占位符的值之外不执行任何操作
<INSTALL_DIR>。调用者可以使用<INSTALL_DIR>
占位符将该信息传递到需要的地方。项目应该使用INSTALL_DIR来定义位置,然后使用
<INSTALL_DIR>占位符,而不是直接将路径嵌入到INSTALL_COMMAND. 这确保了以后可以在需要时查询该位置,如下面第 28.1.3 节“其他功能”所述。
The INSTALL_DIR option doesn’t do anything other than define a value for the
<INSTALL_DIR> placeholder. It is up to the caller to use the <INSTALL_DIR>
placeholder to pass that information through to wherever it is needed. Projects
should use INSTALL_DIR to define the location and then use the
<INSTALL_DIR> placeholder rather than embedding the path directly in options
like INSTALL_COMMAND. This ensures that the location can be queried later if
required, as covered in Section 28.1.3, “Miscellaneous Features” further below.
测试阶段的处理方式略有不同,默认情况下不执行任何操作。要启用它,必须给出特定于测试的选项之一,例如
TEST_BEFORE_INSTALL YES或TEST_AFTER_INSTALL YES。启用后,该模式与构建和安装阶段相同,test默认情况下使用适当的构建工具调用目标,但TEST_COMMAND可以提供替代行为。
The test stage is handled slightly differently and does nothing by default.
To enable it, one of the test-specific options must be given, such as
TEST_BEFORE_INSTALL YES or TEST_AFTER_INSTALL YES. Once enabled, the
pattern is the same as for the build and install stages, with the appropriate
build tool invoking the test target by default, but TEST_COMMAND can be
given to provide alternative behavior.
当然,ExternalProject除了基本的下载 URL 之外,还有更多的下载支持。对于档案,它支持主项目提供要下载的文件的哈希值。这不仅具有验证下载内容的明显优势,还允许模块检查之前可能下载的文件,如果知道已经有具有正确哈希值的文件,则避免再次重新下载。哈希值可以是该file()命令支持的任何算法,但通常是MD5
或SHA1。URL_HASH哈希值由以下示例中的选项给出:
Of course, ExternalProject has considerably more downloading support than
just a basic URL to download. For archives, it supports the main project giving
a hash of the file to be downloaded. This not only has the obvious advantage of
verifying the downloaded contents, it also allows the module to check a file it
might have downloaded previously and avoid re-downloading it again if it knows
it already has one with the correct hash. The hash value can be for any
algorithm that the file() command supports, but it is typically either MD5
or SHA1. The hash is given with the URL_HASH option as in the following
example:
ExternalProject_Add(SomeAutotoolsProj
URL someUrl
URL_HASH MD5=b4a78fe5c9f2ef73cd3a6b07e79f2283
#... other options
)ExternalProject_Add(SomeAutotoolsProj
URL someUrl
URL_HASH MD5=b4a78fe5c9f2ef73cd3a6b07e79f2283
#... other options
)
强烈建议指定哈希值。URL如果在没有附带选项的情况下使用该选项,CMake 将发出警告
URL_HASH(作为保持与旧 CMake 版本向后兼容性的特殊情况,该
URL_MD5选项可用于提供 MD5 哈希值,但项目应避免使用它,以支持更灵活的选项)URL_HASH选项)。
Specifying a hash is strongly recommended. CMake will issue a warning if the
URL option is used without an accompanying URL_HASH option (as a special
case to maintain backward compatibility with older CMake versions, the
URL_MD5 option can be used to provide a MD5 hash, but projects should avoid
it in favor of the more flexible URL_HASH option).
还可以指定多个 URL,并让项目依次尝试每个 URL,直到其中一个成功。当要连接的可用服务器可能会根据网络连接、VPN 设置等而变化时,或者在可能较慢的远程服务器之前尝试本地服务器时,这会很有用。此功能不能与 url 一起使用file://。
It is also possible to specify more than one URL and let the project try each
in turn until one succeeds. This can be useful when the available servers to
connect to might change depending on the network connection, VPN settings, etc.
or to try local servers before potentially slower remote servers. This feature
cannot be used with file:// urls.
set(archive someproj-1.2.3.tar.gz)
ExternalProject_Add(SomeProj
URL http://mirrors.example.com/releases/${archive}
https://somewhereelse.com/artifacts/${archive}
URL_HASH MD5=b4a78fe5c9f2ef73cd3a6b07e79f2283
#... other options
)set(archive someproj-1.2.3.tar.gz)
ExternalProject_Add(SomeProj
URL http://mirrors.example.com/releases/${archive}
https://somewhereelse.com/artifacts/${archive}
URL_HASH MD5=b4a78fe5c9f2ef73cd3a6b07e79f2283
#... other options
)
下载压缩包时,根据下载后的文件内容检测压缩包格式,并自动解压压缩包。如果需要,可以禁用自动解包,并且可以控制如何配置下载本身的各个方面。有关这些不太常见场景的相关选项的详细信息,请参阅模块文档。
When downloading archives, the archive format is detected based on the file contents after download and the archive is unpacked automatically. The automatic unpacking can be disabled if needed and various aspects of how the download itself is configured can be controlled. See the module documentation for details on the relevant options for these less common scenarios.
下载的内容不必来自存档,该模块还可以直接使用 git、subversion、mercurial 或 CVS 的源代码存储库。其中每一个都需要使用选项来命名存储库<REPOTYPE>_REPOSITORY
,然后还可以给出其他存储库特定的选项。
Downloaded contents don’t have to be from an archive, the module can also work
directly with source code repositories for git, subversion, mercurial or CVS.
Each of these require the repository to be named with a <REPOTYPE>_REPOSITORY
option and then other repository specific options may also be given.
ExternalProject_Add(MyProj
GIT_REPOSITORY git@example.com/git/myproj.git
GIT_TAG 3a281711d1243351190bdee50a40d81694aa630a
)ExternalProject_Add(MyProj
GIT_REPOSITORY git@example.com/git/myproj.git
GIT_TAG 3a281711d1243351190bdee50a40d81694aa630a
)
上面的示例显示了克隆 git 存储库和签出特定提交所需的典型信息。如果省略该选项,则将使用GIT_TAG默认分支(通常)上的最新提交。标签或分支的名称也可以代替提交哈希来master给出。GIT_TAG虽然GIT_TAG确实支持这些不同的选择,但应该注意的是,只有提交哈希才是真正明确的。使用 git,分支或标记名称引用的提交可以随着时间的推移而移动,因此使用它们并不能保证可重复的构建。类似地,完全省略GIT_TAG与给出默认分支的名称相同,因此它也不会总是指向相同的提交。
The above example shows the typical information needed to clone a git
repository and checkout a particular commit. If the GIT_TAG option is
omitted, the latest commit on the default branch (usually master) will be
used. The name of a tag or branch can also be given with GIT_TAG instead of a
commit hash. While GIT_TAG does support these different choices, it should be
noted that only a commit hash is truly unambiguous. With git, the commit
referenced by a branch or tag name can move over time, so using them does not
guarantee a repeatable build. Similarly, omitting GIT_TAG altogether is the
same as giving the name of the default branch, so it too won’t always point at
the same commit.
仅使用提交哈希还有另一个原因GIT_TAG。由于标签或分支可能会随着时间的推移而改变,因此ExternalProject_Add()每次运行 CMake 时可能都需要联系远程端,即使本地已经有指定的标签或分支。如果没有从远程获取,CMake 无法确定标记或分支是否未移动。每次重新运行 CMake 时的往返成本可能会很高,特别是当项目使用许多外部项目时。作为一种特殊情况,如果请求标签并且该标签已经签出,则将假定该标签尚未移动并且不会执行任何提取。对于大多数标签一旦创建就不会移动的项目来说,性能和稳健性之间的这种权衡通常是安全的,但它确实为不可复制的构建开辟了一条途径。如果改用提交哈希,则ExternalProject_Add()可以可靠地知道本地是否已经有提交,而无需联系远程。
There is another reason to only use commit hashes with GIT_TAG.
Because a tag or branch can change over time, ExternalProject_Add() may need
to contact the remote end every time CMake is run, even if it already has the
named tag or branch locally.
CMake cannot be sure that the tag or branch hasn’t moved without fetching from
the remote.
This round trip every time CMake is re-run can be expensive, especially if the
project is using many external projects.
As a special case, if a tag is requested and that tag is already checkout out,
it will be assumed that the tag hasn’t moved and no fetch will be performed.
This trade-off between performance and robustness is typically safe for most
projects where tags never move once created, but it does open an avenue for a
non-reproducible build.
If a commit hash is used instead, ExternalProject_Add() knows robustly
whether it already has the commit locally without needing to contact the
remote.
其他选项可用于自定义 git 行为,包括指定不同的默认远程名称、git 子模块的控制、浅克隆和任意 git 配置选项。有关更多详细信息,请参阅模块文档。
Other options can be used to customize the git behavior, including specifying a different default remote name, control of git submodules, shallow clones and arbitrary git config options. Consult the module documentation for further details.
从 subversion 存储库中签出与 git 非常相似:
Checking out from a subversion repository is fairly similar to git:
ExternalProject_Add(MyProj
SVN_REPOSITORY svn+ssh@example.com/svn/myproj/trunk
SVN_REVISION -r31227
)ExternalProject_Add(MyProj
SVN_REPOSITORY svn+ssh@example.com/svn/myproj/trunk
SVN_REVISION -r31227
)
该SVN_REVISION选项指定一个svn命令行选项,该选项预期指定要签出的提交。这通常是使用-r上面所示的选项指定的全局修订号,但技术上可以是任何有效的命令行选项。如果SVN_REVISION省略,则使用最新版本,但项目应努力始终提供此选项以确保构建是可重复的。还支持一些其他与安全相关的颠覆选项ExternalProject_Add(),例如用于使用存储库进行身份验证和指定证书信任设置。ExternalProject有关这些不常用选项的详细信息,请参阅模块文档。
The SVN_REVISION option specifies a svn command line option that is
expected to specify the commit to check out. This will frequently be a global
revision number specified with the -r option as shown above, but could
technically be any valid command line option. If SVN_REVISION is omitted, the
latest revision is used, but projects should strive to always provide this
option to ensure that the build is repeatable. A few other security-related
subversion options are also supported by ExternalProject_Add(), such as for
authenticating with the repository and specifying certificate trust settings.
Consult the ExternalProject module documentation for details on these less
frequently used options.
相比之下,对 Mercurial 和 CVS 的支持非常基础。对于 Mercurial,只能指定存储库和标签,而对于 CVS,还需要该模块:
In comparison, the support for Mercurial and CVS is very basic. In the case of Mercurial, only the repository and tag can be specified, while for CVS the module is also required:
ExternalProject_Add(MyProjHg
HG_REPOSITORY http://example.com/hg/myproj
HG_TAG dd2ce38a6b8a
)
ExternalProject_Add(MyProjCVS
CVS_REPOSITORY http://example.com/cvs/myproj
CVS_MODULE someModule
CVS_TAG -rsomeTag
)ExternalProject_Add(MyProjHg
HG_REPOSITORY http://example.com/hg/myproj
HG_TAG dd2ce38a6b8a
)
ExternalProject_Add(MyProjCVS
CVS_REPOSITORY http://example.com/cvs/myproj
CVS_MODULE someModule
CVS_TAG -rsomeTag
)
该选项与直接放置在命令行上的选项CVS_TAG类似,因此它必须包含任何必需的命令选项前缀,如上所示。SVN_REVISIONcvs
The CVS_TAG option is analogous to the SVN_REVISION option in that it is
placed on the cvs command line directly, so it must include any required
command option prefix such as shown above.
有时,引用序列中的其中一个步骤可能很有用,甚至有必要ExternalProject,例如添加对为特定步骤提供输入的另一个 CMake 目标的依赖项。STEP_TARGETS可以提供该选项ExternalProject_Add()来告诉它为指定的步骤集创建 CMake 目标。这些目标的名称格式为
mainName-step,其中mainName是作为第一个参数给出的目标名称ExternalProject_Add(),step是目标代表的步骤。例如,以下内容将导致命名
MyProj-configure并MyProj-install定义目标:
Sometimes it can be useful or even necessary to refer to one of the steps in
the ExternalProject sequence, such as to add a dependency on another CMake
target that provides an input to a particular step. The STEP_TARGETS option
can be given to ExternalProject_Add() to tell it to create CMake targets for
the specified set of steps. These targets have names of the form
mainName-step, where mainName is the target name given as the first
argument to ExternalProject_Add() and step is the step the target
represents. For example, the following would result in targets named
MyProj-configure and MyProj-install being defined:
ExternalProject_Add(MyProj
GIT_REPOSITORY git@example.com/git/myproj.git
GIT_TAG 3a281711d1243351190bdee50a40d81694aa630a
STEP_TARGETS configure install
)ExternalProject_Add(MyProj
GIT_REPOSITORY git@example.com/git/myproj.git
GIT_TAG 3a281711d1243351190bdee50a40d81694aa630a
STEP_TARGETS configure install
)
为这些步骤目标添加依赖项需要更加小心。要使步骤目标依赖于其他 CMake 目标,项目应使用
ExternalProject_Add_StepDependencies()模块提供的函数而不是调用add_dependencies(). 这可以确保正确处理步骤时间戳等内容。该命令的形式如下:
Adding dependencies for these step targets requires a little more care. To make
a step target depend on some other CMake target, the project should use the
ExternalProject_Add_StepDependencies() function provided by the module
rather than calling add_dependencies(). This ensures that things like the
step timestamps are handled correctly. The form of that command is as follows:
ExternalProject_Add_StepDependencies(
mainName step otherTarget1...
)ExternalProject_Add_StepDependencies(
mainName step otherTarget1...
)
以下示例展示了如何使用此函数使上configure
一个示例的步骤依赖于主项目构建的可执行文件:
The following example shows how to use this function to make the configure
step of the previous example depend on an executable built by the main project:
add_executable(PreConfigure ...)
ExternalProject_Add_StepDependencies(
MyProj configure PreConfigure
)add_executable(PreConfigure ...)
ExternalProject_Add_StepDependencies(
MyProj configure PreConfigure
)
要使普通 CMake 目标依赖于步骤目标,add_dependencies()
就可以了:
To make an ordinary CMake target depend on a step target, add_dependencies()
is fine:
add_executable(PostInstall ...)
add_dependencies(PostInstall MyProj-install)add_executable(PostInstall ...)
add_dependencies(PostInstall MyProj-install)
如果一个外部项目的特定步骤需要依赖于另一外部项目的步骤,ExternalProject_Add_StepDependencies()则必须再次使用:
If a particular step of one external project needs to depend on a step of a
different external project, ExternalProject_Add_StepDependencies() must once
again be used:
ExternalProject_Add(Earlier
STEP_TARGETS build
...
)
ExternalProject_Add(Later
STEP_TARGETS build
...
)
ExternalProject_Add_StepDependencies(
Later build Earlier-build
)ExternalProject_Add(Earlier
STEP_TARGETS build
...
)
ExternalProject_Add(Later
STEP_TARGETS build
...
)
ExternalProject_Add_StepDependencies(
Later build Earlier-build
)
Earlier如果定义运行起来很耗时的测试,则上述安排可能很有用,但在并行构建中Later,项目不需要等待这些测试,只需Earlier构建即可。
The above arrangement can be useful if Earlier defines tests that are time
consuming to run, but in a parallel build the Later project doesn’t need to
wait for those tests, only for Earlier to be built.
当需要为多个外部项目定义同一组步骤目标时,可以通过设置目录EP_STEP_TARGETS属性将它们设置为默认值,而不是每次都重复它们。
When the same set of step targets need to be defined for multiple external
projects, rather than repeating them each time, they can be made the default
by setting the EP_STEP_TARGETS directory property instead.
set_property(DIRECTORY PROPERTY EP_STEP_TARGETS build)
ExternalProject_Add(Earlier ...)
ExternalProject_Add(Later ...)
ExternalProject_Add_StepDependencies(
Later build Earlier-build
)set_property(DIRECTORY PROPERTY EP_STEP_TARGETS build)
ExternalProject_Add(Earlier ...)
ExternalProject_Add(Later ...)
ExternalProject_Add_StepDependencies(
Later build Earlier-build
)
但对于许多项目来说,这种依赖关系的粒度只能带来有限的收益,而且复杂性可能不值得。DEPENDS通过使用with 选项
,可以使整个外部项目依赖于另一个目标ExternalProject_Add(),这要简单得多:
For many projects though, such granularity of dependencies offers only limited
gains and the complexity may not be worth it. The whole external project can be
made dependent on another target by using the DEPENDS option with
ExternalProject_Add(), which is much simpler:
add_executable(PreConfigure ...)
ExternalProject_Add(MyProj
DEPENDS PreConfigure
...
)add_executable(PreConfigure ...)
ExternalProject_Add(MyProj
DEPENDS PreConfigure
...
)
该DEPENDS选项负责确保正确处理所有步骤依赖关系,就像ExternalProject_Add_StepDependencies()设置更细粒度的依赖关系时一样。
The DEPENDS option takes care of ensuring all of the step dependencies are
handled correctly just as ExternalProject_Add_StepDependencies() does when
setting up more granular dependencies.
项目不仅限于默认步骤,它们可以创建自己的自定义步骤,并将其插入具有所需依赖关系的步骤工作流中。该ExternalProject_Add_Step()函数提供了此功能:
Projects are not limited to only the default steps, they can create their own
custom steps and insert them into the step workflow with whatever dependency
relationships they require. The ExternalProject_Add_Step() function
provides this capability:
ExternalProject_Add_Step(mainName step
[COMMAND command [args...] ]
[COMMENT comment]
[WORKING_DIRECTORY dir]
[DEPENDS filesWeDependOn...]
[DEPENDEES stepsWeDependOn...]
[DEPENDERS stepsDependOnUs...]
[BYPRODUCTS byproducts...]
[INDEPENDENT bool] # CMake 3.19 or later
[ALWAYS bool]
[EXCLUDE_FROM_MAIN bool]
[LOG bool]
[USES_TERMINAL bool]
)ExternalProject_Add_Step(mainName step
[COMMAND command [args...] ]
[COMMENT comment]
[WORKING_DIRECTORY dir]
[DEPENDS filesWeDependOn...]
[DEPENDEES stepsWeDependOn...]
[DEPENDERS stepsDependOnUs...]
[BYPRODUCTS byproducts...]
[INDEPENDENT bool] # CMake 3.19 or later
[ALWAYS bool]
[EXCLUDE_FROM_MAIN bool]
[LOG bool]
[USES_TERMINAL bool]
)
COMMAND用于定义执行步骤时要采取的操作。它类似于可以为每个默认步骤指定的自定义命令。COMMENT可以在执行步骤时提供自定义消息,但如第 18.1 节“自定义目标”中所述,此类注释并不总是显示,因此请认为它们有帮助,但不是必需的。该
WORKING_DIRECTORY选项与命令的含义相同add_custom_target()
。
COMMAND is used to define the action to take when the step is executed. It
is analogous to the custom command that can be specified for each of the
default steps. COMMENT can be supplied to provide a custom message when
executing the step, but as noted back in Section 18.1, “Custom Targets”, such comments are
not always shown, so consider them helpful but not essential. The
WORKING_DIRECTORY option has the same meaning as for an add_custom_target()
command.
可以通过自定义步骤提供全面的依赖项详细信息。如果命令依赖于特定文件或文件集,则应使用选项列出它们DEPENDS。对于作为构建的一部分生成的文件,它们必须由在同一目录范围中创建的自定义命令生成。和
选项定义此自定义步骤在外部项目的步骤工作流中的位置DEPENDEES。DEPENDERS必须注意完全指定所有直接依赖项,否则自定义步骤可能会不按顺序执行。BYPRODUCTS如果自定义步骤生成外部项目或主项目中其他内容所依赖的文件,也应该使用该选项。如果没有这个,Ninja 生成器可能会抱怨缺少构建规则。
Comprehensive dependency details can be provided with the custom step. If the
command depends on a specific file or set of files, they should be listed
with the DEPENDS option. For files generated as part of the build, they must
be generated by custom commands created in the same directory scope. The
DEPENDEES and DEPENDERS options define where this custom step sits in the
step workflow of the external project. Care must be taken to fully specify all
direct dependencies or else the custom step will potentially execute out of
sequence. The BYPRODUCTS option should also be used if the custom step
produces a file that something else in the external project or main project
depends on. Without this, the Ninja generators will likely complain about a
missing build rule.
CMake 3.19 添加了对INDEPENDENT关键字的支持,该关键字后面必须跟一个布尔 true 或 false 值。如果该步骤不依赖于该外部项目之外的任何内容,则可以将其指定为独立的。它可能仍然依赖于mainName外部项目中的其他步骤,只要这些其他步骤也被认为是独立的。例如,update步骤依赖于download步骤,但两者都是独立创建的,因为它们不依赖于构建其他部分的任何目标。将步骤指定为独立可以提高并行性,并且可以避免在不相关的外部依赖项发生更改时不必要地重新执行该步骤。如果该INDEPENDENT选项的值为 false 或不存在,则步骤目标将取决于mainName目标所依赖的所有内容。读者可参阅策略CMP0114文档,其中相当深入地讨论了此功能的使用、限制和相关历史记录。
CMake 3.19 added support for the INDEPENDENT keyword, which must be followed
by a boolean true or false value.
Where the step does not depend on anything outside of this external project,
it can be specified as independent.
It may still depend on other steps within the mainName external project as
long as those other steps are also considered independent.
For example, the update step depends on the download step, but both are
created as independent because they don’t rely on any targets from other parts
of the build.
Specifying a step to be independent can improve parallelism and may avoid
unnecessarily re-executing the step when unrelated external dependencies
change.
If the INDEPENDENT option is given a value of false or is not present, the
step target will depend on everything that the mainName target depends on.
The reader is referred to the policy CMP0114 documentation, which discusses
the use, limitations and relevant historical notes around this functionality
in considerable depth.
ALWAYS
通过将该选项设置为 true,可以使自定义步骤始终显示为过时。项目通常应该仅在没有其他步骤依赖于它的情况下才执行此操作,因为依赖于它的任何内容也将始终被认为是过时的,这可能会导致构建完成比其需要的更多工作。如果自定义步骤仅根据需要构建,则将 和 设置ALWAYS
为EXCLUDE_FROM_MAINtrue 通常是所需的组合。其余选项LOG将USES_TERMINAL在下一节中讨论。
A custom step can be made to always appear out of date by setting the ALWAYS
option to true. Projects should generally only do this if no other step depends
on it, since anything depending on it would then also be always considered out
of date and this may lead to builds doing more work than they need to. If the
custom step is intended to only be built on demand, then setting both ALWAYS
and EXCLUDE_FROM_MAIN to true is usually the desired combination. The
remaining options LOG and USES_TERMINAL are discussed in the next section.
所有默认步骤都是通过ExternalProject_Add_Step()
从 inside 调用内部创建的ExternalProject_Add()。项目不得尝试重新定义它们,这意味着自定义步骤不能命名为
mkdir、download、update、skip-update、patch、configure、build、
install或test。
All of the default steps are created by calls to ExternalProject_Add_Step()
internally from within ExternalProject_Add(). Projects must not try to
redefine them, which means custom steps cannot be named
mkdir, download, update, skip-update, patch, configure, build,
install or test.
操作和步骤间依赖关系由 定义
ExternalProject_Add_Step(),但为了为自定义步骤创建目标,ExternalProject_Add_StepTargets()还必须调用该函数。该函数也会在内部调用,以为
其选项中列出的步骤或通过目录属性设置的ExternalProject_Add()步骤创建目标
。STEP_TARGETSEP_STEP_TARGETS
The actions and inter-step dependencies are defined by
ExternalProject_Add_Step(), but in order for a target to be created for a
custom step, the ExternalProject_Add_StepTargets() function must be
called as well. This function is also called internally by
ExternalProject_Add() to create targets for steps listed in its
STEP_TARGETS option or set via the EP_STEP_TARGETS directory property.
ExternalProject_Add_StepTargets(
mainName [NO_DEPENDS] steps...
)ExternalProject_Add_StepTargets(
mainName [NO_DEPENDS] steps...
)
该NO_DEPENDS选项很少是可取的,并且在大多数情况下不推荐。CMP0114它自 CMake 3.19 起已弃用,并且如果策略设置为则不可用NEW。有关更多详细信息,请参阅模块文档中对此选项的讨论。
The NO_DEPENDS option is rarely desirable and is not recommended for most
scenarios.
It is deprecated since CMake 3.19 and is not available if policy CMP0114 is
set to NEW.
See the discussion of this option in the module documentation for
further details.
以下示例演示如何定义package依赖于build步骤的自定义步骤,但仅在明确请求时执行:
The following example demonstrates how to define a package custom step which
depends on the build step, but is only executed when explicitly requested:
ExternalProject_Add_Step(MyProj package
COMMAND ${CMAKE_COMMAND}
--build <BINARY_DIR>
--target package
DEPENDEES build
ALWAYS YES
EXCLUDE_FROM_MAIN YES
)
ExternalProject_Add_StepTargets(MyProj package)ExternalProject_Add_Step(MyProj package
COMMAND ${CMAKE_COMMAND}
--build <BINARY_DIR>
--target package
DEPENDEES build
ALWAYS YES
EXCLUDE_FROM_MAIN YES
)
ExternalProject_Add_StepTargets(MyProj package)
对于任何默认或自定义步骤,都可以指定自定义命令。对于
ExternalProject_Add(),相关选项是以 结尾的选项
_COMMAND,而对于ExternalProject_Add_Step()则是COMMAND提供要执行的自定义命令的选项。COMMAND这两个函数都允许通过在第一个命令后附加更多选项来给出多个命令。然后按该步骤的顺序执行每个命令。
For any of the default or custom steps, a custom command can be specified. For
ExternalProject_Add(), the relevant options are those that end with
_COMMAND, while for ExternalProject_Add_Step() it is the COMMAND option
that provides the custom command to execute. Both of these functions allow more
than one command to be given by appending further COMMAND options that
immediately follow the first. Each command is then executed in order for that
step.
ExternalProject_Add(MyProj
CONFIGURE_COMMAND
${CMAKE_COMMAND} -E echo "Starting configure"
COMMAND ./configure
COMMAND ${CMAKE_COMMAND} -E echo "Configure completed"
BUILD_COMMAND
${CMAKE_COMMAND} -E echo "Starting build"
COMMAND ${MAKE_EXECUTABLE} MySpecialTarget
COMMAND ${CMAKE_COMMAND} -E echo "Build completed"
)
ExternalProject_Add_Step(MyProj package
COMMAND ${CMAKE_COMMAND} -E echo "Starting packaging"
COMMAND ${CMAKE_COMMAND}
--build <BINARY_DIR>
--target package
COMMAND ${CMAKE_COMMAND} -E echo "Packaging completed"
DEPENDEES build
ALWAYS YES
EXCLUDE_FROM_MAIN YES
)
ExternalProject_Add_StepTargets(MyProj package)ExternalProject_Add(MyProj
CONFIGURE_COMMAND
${CMAKE_COMMAND} -E echo "Starting configure"
COMMAND ./configure
COMMAND ${CMAKE_COMMAND} -E echo "Configure completed"
BUILD_COMMAND
${CMAKE_COMMAND} -E echo "Starting build"
COMMAND ${MAKE_EXECUTABLE} MySpecialTarget
COMMAND ${CMAKE_COMMAND} -E echo "Build completed"
)
ExternalProject_Add_Step(MyProj package
COMMAND ${CMAKE_COMMAND} -E echo "Starting packaging"
COMMAND ${CMAKE_COMMAND}
--build <BINARY_DIR>
--target package
COMMAND ${CMAKE_COMMAND} -E echo "Packaging completed"
DEPENDEES build
ALWAYS YES
EXCLUDE_FROM_MAIN YES
)
ExternalProject_Add_StepTargets(MyProj package)
命令还可以访问终端,这对于存储库访问(可能需要用户输入私钥密码等)非常重要。虽然这不适用于持续集成构建,但有时对于以下情况很有用:开发人员的日常活动。ExternalProject_Add()对终端的访问通过形式的选项按步骤进行控制
USES_TERMINAL_<STEP>,其中
<STEP>是大写的步骤名称,为选项给出的值是 true 或 false 常量。对于自定义步骤,命令USES_TERMINAL的选项
ExternalProject_Add_Step()具有相同的效果。如果使用 git 或 subversion 存储库进行下载,建议授予下载和更新步骤对终端的访问权限。
Commands can also be given access to the terminal, which can be important for
things like repository access that may require the user to enter a password
for a private key, etc.
While this is not applicable to continuous integration builds, it
is sometimes useful for developers in their day to day activities.
Access to the terminal is controlled on a per-step basis with
ExternalProject_Add() options of the form USES_TERMINAL_<STEP>, where
<STEP> is the uppercased step name and the value given for the option is a
true or false constant. For custom steps, the USES_TERMINAL option for the
ExternalProject_Add_Step() command has the same effect. If using a git or
subversion repository for the download, it is advisable to give the download
and update steps access to the terminal.
ExternalProject_Add(MyProj
GIT_REPOSITORY git@example.com/git/myproj.git
GIT_TAG 3a281711d1243351190bdee50a40d81694aa630a
USES_TERMINAL_DOWNLOAD YES
USES_TERMINAL_UPDATE YES
)ExternalProject_Add(MyProj
GIT_REPOSITORY git@example.com/git/myproj.git
GIT_TAG 3a281711d1243351190bdee50a40d81694aa630a
USES_TERMINAL_DOWNLOAD YES
USES_TERMINAL_UPDATE YES
)
仅应在需要时才允许步骤访问终端。这样做的效果主要与 Ninja 生成器相关,其中自定义步骤将被放入console作业池中。分配给池的所有目标
console都被迫串行运行,并且在其他作业池中并行运行的任何任务的输出都会被缓冲,直到当前console作业完成。请特别小心,除非绝对必要,否则不要让构建步骤访问终端,因为它可能会对项目的整体构建性能产生重大负面影响。
Steps should only be given access to the terminal if it is needed. The effect
of doing so is mostly relevant for the Ninja generators, where the custom step
will be placed into the console job pool. All targets allocated to the
console pool are forced to run serially and the output of any tasks running
in other job pools in parallel is buffered until the current console job
completes. Be especially careful not to give the build step access to the
terminal unless absolutely necessary, since it has the potential to have a
significant negative impact on the overall build performance of the project.
在某些情况下,将各个步骤的输出捕获到文件而不是将其发送到终端可能会很有用。当存在大量输出时,只有在出现错误或其他意外问题时才会对这些输出感兴趣,这尤其有用。要将步骤的输出重定向到文件,请将选项LOG_<STEP>in
ExternalProject_Add()或LOG选项 in设置ExternalProject_Add_Step()为真值。然后,终端输出将仅显示一条短消息,指示该步骤是否成功以及在哪里可以找到日志文件。
In some cases, it can be useful to capture the output from individual steps to
file rather than have it go to the terminal.
This is especially useful where there is a large amount of output
that will only be of interest if there is an error or other unexpected problem.
To redirect a step’s output to file, set the LOG_<STEP> option in
ExternalProject_Add() or the LOG option in ExternalProject_Add_Step() to
a true value. The terminal output will then only show a short message
indicating whether or not the step was successful and where the log files can
be found.
在 CMake 3.14 之前,日志文件始终创建在时间戳目录(即STAMP_DIR)中,这并不总是最直观的位置。在 CMake 3.14 及更高版本中,可以使用关键字指定日志文件目录
LOG_DIR。如果给出相对路径,它将被视为相对于
CMAKE_CURRENT_BINARY_DIR。CMake 3.14 中还添加了该LOG_MERGED_STDOUTERR选项,当它设置为 true 时,每个步骤的stdout和stderr流将合并到一个日志文件中,LOG_<STEP>也设置为 true。
Prior to CMake 3.14, log files were always created in the timestamp
directory (i.e. STAMP_DIR), which isn’t always the most intuitive location.
With CMake 3.14 and later, the log file directory can be specified with the
LOG_DIR keyword.
If a relative path is given, it will be treated as being relative to
CMAKE_CURRENT_BINARY_DIR.
The LOG_MERGED_STDOUTERR option was also added in CMake 3.14 and when it is
set to true, the stdout and stderr streams will be merged into a single
log file for each step with LOG_<STEP> also set to true.
ExternalProject_Add(MyProj
GIT_REPOSITORY git@example.com/git/myproj.git
GIT_TAG 3a281711d1243351190bdee50a40d81694aa630a
LOG_BUILD YES
LOG_TEST YES
# The following require CMake 3.14 or later
LOG_DIR logs
LOG_MERGED_STDOUTERR YES
)ExternalProject_Add(MyProj
GIT_REPOSITORY git@example.com/git/myproj.git
GIT_TAG 3a281711d1243351190bdee50a40d81694aa630a
LOG_BUILD YES
LOG_TEST YES
# The following require CMake 3.14 or later
LOG_DIR logs
LOG_MERGED_STDOUTERR YES
)
项目可能想知道选项的价值ExternalProject_Add()。诸如 之类的占位符<SOURCE_DIR>涵盖了在调用 时需要值的许多常见场景
ExternalProject_Add(),但对于其他情况,该
ExternalProject_Get_Property()命令很有用:
A project may want to know the value of an ExternalProject_Add() option.
Placeholders such as <SOURCE_DIR> cover many of the common scenarios where
values are needed within the call to
ExternalProject_Add(), but for other cases the
ExternalProject_Get_Property() command is useful:
ExternalProject_Get_Property(mainName propertyName...)ExternalProject_Get_Property(mainName propertyName...)
它的语法与其他属性检索命令(例如
get_property(). 没有给出输出变量名称,而是创建与要检索的属性名称匹配的变量。这允许在一次调用中检索多个属性。
Its syntax differs significantly from other property retrieval commands like
get_property().
No output variable name is given, instead a variable matching the name of the
property to be retrieved is created.
This allows multiple properties to be retrieved in one call.
ExternalProject_Get_Property(MyProj SOURCE_DIR LOG_BUILD)
set(msg "MyProj source will be in ${SOURCE_DIR}")
if(LOG_BUILD)
string(APPEND msg
" and its build output will be redirected to files"
)
endif()
message(STATUS "${msg}")ExternalProject_Get_Property(MyProj SOURCE_DIR LOG_BUILD)
set(msg "MyProj source will be in ${SOURCE_DIR}")
if(LOG_BUILD)
string(APPEND msg
" and its build output will be redirected to files"
)
endif()
message(STATUS "${msg}")
如果正确使用,该ExternalProject模块既强大又有效,但有时也会导致难以追踪的问题。最常见的问题之一是尝试设置多个外部项目时,其中一个项目希望能够使用另一个项目的构建输出。这一般需要主项目做两件事:
The ExternalProject module is both powerful and effective when used
correctly, but it can also sometimes lead to problems that can be difficult to
trace. One of the most common problems encountered is when trying to
set up multiple external projects where one project wants to be able to use
build outputs from another. This generally requires the main project to do two
things:
通过为依赖者的配置步骤创建对依赖者的主要目标的依赖关系,第一点很容易建立。第二点需要了解受抚养人如何想知道受抚养人的位置。例如,如果它使用find_package()、
find_library()等来定位依赖者,那么设置它
CMAKE_PREFIX_PATH可能就足够了。以下示例演示了此技术,将zlib和构建libpng为外部项目并将它们安装到同一目录。既然libpng
需要zlib,就给它通用的安装区域以CMAKE_PREFIX_PATH
允许它找到zlib。该示例确保在运行其配置步骤zlib之前安装
libpng,即何时libpng使用
CMAKE_PREFIX_PATH.
The first point is easy enough to establish by creating a dependency
for the configure step of the depender on the main target of the dependee. The
second point requires understanding how the depender wants to know about the
location of the dependee. For example, if it uses find_package(),
find_library(), etc. to locate the dependee, then setting its
CMAKE_PREFIX_PATH may be sufficient. The following example
demonstrates this technique, building both zlib and libpng as external
projects and installing them to the same directory. Since libpng
requires zlib, giving it the common install area for CMAKE_PREFIX_PATH
allows it to find zlib. The example ensures zlib is installed before
libpng runs its configure step, which is when libpng will use
CMAKE_PREFIX_PATH.
set(installDir ${CMAKE_CURRENT_BINARY_DIR}/install)
include(ExternalProject)
ExternalProject_Add(zlib
INSTALL_DIR ${installDir}
URL https://zlib.net/zlib-1.2.11.tar.gz
URL_HASH SHA256=c3e5e9fdd5 # Shortened for space
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)
set(pngHost ftp://ftp-osl.osuosl.org)
set(pngPath pub/libpng/src/libpng16/libpng-1.6.34.tar.gz)
ExternalProject_Add(libpng
INSTALL_DIR ${installDir}
URL ${pngHost}/${pngPath}
URL_HASH MD5=03fbc5134830240104e96d3cda648e71
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
-DCMAKE_PREFIX_PATH:PATH=<INSTALL_DIR>
)
ExternalProject_Add_StepDependencies(libpng configure zlib)set(installDir ${CMAKE_CURRENT_BINARY_DIR}/install)
include(ExternalProject)
ExternalProject_Add(zlib
INSTALL_DIR ${installDir}
URL https://zlib.net/zlib-1.2.11.tar.gz
URL_HASH SHA256=c3e5e9fdd5 # Shortened for space
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)
set(pngHost ftp://ftp-osl.osuosl.org)
set(pngPath pub/libpng/src/libpng16/libpng-1.6.34.tar.gz)
ExternalProject_Add(libpng
INSTALL_DIR ${installDir}
URL ${pngHost}/${pngPath}
URL_HASH MD5=03fbc5134830240104e96d3cda648e71
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
-DCMAKE_PREFIX_PATH:PATH=<INSTALL_DIR>
)
ExternalProject_Add_StepDependencies(libpng configure zlib)
使用 Ninja 生成器时可能出现的另一个与依赖关系相关的问题是 Ninja 抱怨它不知道如何构建外部项目应该提供的特定文件。下面的例子演示了这种情况。
Another dependency-related issue that can arise when using the Ninja generators is Ninja complaining that it doesn’t know how to build a particular file that an external project is supposed to be supplying. The following example demonstrates such a situation.
ExternalProject_Add(MyProj
# Options to download and build a library "someLib"
...
)
ExternalProject_Get_Property(MyProj INSTALL_DIR)
add_library(MyProj::someLib STATIC IMPORTED)
set_target_properties(MyProj::someLib PROPERTIES
# Platform-specific due to hard-coded library location
# and file name
IMPORTED_LOCATION ${INSTALL_DIR}/lib/libsomeLib.a
)
add_dependencies(MyProj::someLib MyProj)ExternalProject_Add(MyProj
# Options to download and build a library "someLib"
...
)
ExternalProject_Get_Property(MyProj INSTALL_DIR)
add_library(MyProj::someLib STATIC IMPORTED)
set_target_properties(MyProj::someLib PROPERTIES
# Platform-specific due to hard-coded library location
# and file name
IMPORTED_LOCATION ${INSTALL_DIR}/lib/libsomeLib.a
)
add_dependencies(MyProj::someLib MyProj)
Ninja 生成器将尝试在预期位置查找,但在首次构建外部项目libsomeLib.a之前该位置尚不存在。MyProj然后,Ninja 将停止并显示错误,指出它不知道如何构建缺少的依赖项。其他生成器在依赖项检查方面可能更加宽松并且不会抱怨,但这不应被视为对正确指定依赖项的确认。上述问题的解决方案是在调用BUILD_BYPRODUCTS中添加一个选项ExternalProject_Add()以指定构建输出(在 CMake 3.2 或更高版本中可用)。然后,Ninja 将拥有满足其依赖性所需的所有信息。
The Ninja generators will try to find libsomeLib.a at the expected location,
but it won’t yet exist before the MyProj external project is built for the
first time. Ninja will then halt with an error saying it doesn’t know how to
build the missing dependency. Other generators may be more relaxed in their
dependency checking and not complain, but that should not be considered
confirmation of correctly specified dependencies. A solution to the above is to
add a BUILD_BYPRODUCTS option to the ExternalProject_Add() call to specify
the build outputs (available in CMake 3.2 or later). Ninja will then have all
the information it needs to satisfy its dependencies.
ExternalProject_Add(MyProj
BUILD_BYPRODUCTS <INSTALL_DIR>/lib/libsomeLib.a
# Options to download and build the above library
...
)ExternalProject_Add(MyProj
BUILD_BYPRODUCTS <INSTALL_DIR>/lib/libsomeLib.a
# Options to download and build the above library
...
)
ExternalProject上述情况是与主项目中定义的目标混合时出现的问题的示例。这很难稳健地完成,并且通常涉及必须手动指定 CMake 通常代表项目处理的特定于平台的详细信息(例如库名称和位置)。项目应考虑超级构建安排是否更合适,而不是在使用时尝试创建自己的构建目标ExternalProject。
The above situation is an example of the sort of problems that arise when
mixing ExternalProject with targets defined in the main project. This is
difficult to do robustly and usually involves having to manually specify
platform specific details that CMake normally handles on the project’s behalf
(e.g. library names and locations). Projects should consider whether a
superbuild arrangement would be more appropriate and not try to create build
targets of their own when using ExternalProject.
在其他情况下也可能出现依赖性问题。考虑前面的示例,其中ExternalProject用于使用与主构建不同的工具链来构建固件工件。
Dependency problems can also arise in other situations. Consider the earlier
example where ExternalProject was used to enable building firmware artifacts
with a different toolchain to the main build.
set(toolchainFile
${CMAKE_CURRENT_LIST_DIR}/fwtoolchain.cmake
)
ExternalProject_Add(Firmware
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/Firmware
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/Firmware-artifacts
CMAKE_ARGS
-D CMAKE_TOOLCHAIN_FILE=${toolchainFile}
-D CMAKE_BUILD_TYPE=Release
-D CMAKE_INSTALL_PREFIX=<INSTALL_DIR>
)set(toolchainFile
${CMAKE_CURRENT_LIST_DIR}/fwtoolchain.cmake
)
ExternalProject_Add(Firmware
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/Firmware
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/Firmware-artifacts
CMAKE_ARGS
-D CMAKE_TOOLCHAIN_FILE=${toolchainFile}
-D CMAKE_BUILD_TYPE=Release
-D CMAKE_INSTALL_PREFIX=<INSTALL_DIR>
)
上面的内容将成功构建,并且一切看起来都井然有序。如果开发人员随后对源目录中的源进行更改Firmware,则主项目将不会重建固件目标。这是因为ExternalProject使用时间戳来记录步骤的成功完成,因此除非计算依赖项的方式发生变化,否则主项目认为该Firmware项目仍然是最新的。可以通过Firmware使用以下选项强制构建目标始终被视为过时来解决此问题BUILD_ALWAYS:
The above would build successfully and all would appear to be in order. If the
developer then went and made a change to the sources in the Firmware source
directory, the main project would not rebuild the firmware targets. This is
because ExternalProject uses timestamps to record successful completion of
the steps, so unless something changes in the way the dependencies are
computed, the main project thinks the Firmware project is still up to date.
This can be addressed by forcing the Firmware build target to always be
considered out of date using the BUILD_ALWAYS option:
set(toolchainFile
${CMAKE_CURRENT_LIST_DIR}/fwtoolchain.cmake
)
ExternalProject_Add(Firmware
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/Firmware
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/Firmware-artifacts
CMAKE_ARGS
-D CMAKE_TOOLCHAIN_FILE=${toolchainFile}
-D CMAKE_BUILD_TYPE=Release
-D CMAKE_INSTALL_PREFIX=<INSTALL_DIR>
BUILD_ALWAYS YES
)set(toolchainFile
${CMAKE_CURRENT_LIST_DIR}/fwtoolchain.cmake
)
ExternalProject_Add(Firmware
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/Firmware
INSTALL_DIR
${CMAKE_CURRENT_BINARY_DIR}/Firmware-artifacts
CMAKE_ARGS
-D CMAKE_TOOLCHAIN_FILE=${toolchainFile}
-D CMAKE_BUILD_TYPE=Release
-D CMAKE_INSTALL_PREFIX=<INSTALL_DIR>
BUILD_ALWAYS YES
)
这将导致Firmware每次构建主项目时都会调用项目的构建工具。如果项目中没有任何更改Firmware
,则其构建步骤将不会执行任何实际工作,但如果发生了更改,则任何已过时的内容都将按预期重建。设置为 true 的主要缺点BUILD_ALWAYS是,它实际上使主要外部项目目标对于主构建来说总是显得过时,因此即使实际上没有任何内容需要重建,主构建也永远不会是真正的无操作。
This will result in the Firmware project’s build tool being invoked every
time the main project is built. If nothing has changed in the Firmware
project, its build step will do no actual work, but if there has been a change,
then anything that has become out of date will be rebuilt as expected.
The main drawback to setting BUILD_ALWAYS to true is that it effectively
makes the main external project target always appear out of date to the main
build, so the main build will never be a true no-op even when nothing actually
needs rebuilding.
的一些优点ExternalProject也是它的缺点。它允许外部项目构建与主项目完全隔离,因此它可以使用不同的工具链、针对不同的平台、使用不同的构建类型甚至完全不同的构建系统。这些好处的代价是主项目对外部项目产生了什么一无所知。如果主构建中的任何内容需要引用外部项目的输出,则必须手动向主构建提供该信息。这是 CMake 代表项目做的事情,因此ExternalProject以这种方式使用可能是一种倒退。
Some of the strengths of ExternalProject are also its weaknesses. It allows
external project builds to be completely isolated from the main project, so it
can use a different toolchain, target a different platform, use a different
build type or even an entirely different build system. The cost of these
benefits is that the main project knows nothing about what the external project
produces. That information has to be provided to the main build manually if
anything in the main build needs to refer to the external project’s outputs.
This is the sort of thing that CMake is meant to do on the project’s behalf, so
it can be a backward step to use ExternalProject in this way.
对于也使用 CMake 作为构建系统的外部项目,使用与主项目不同的设置来构建它的灵活性通常是不必要的。事实上,更常见的情况可能是外部项目应该使用与主项目相同的设置来构建,但这并不是那么容易使用ExternalProject. 通常,更方便的安排是将其直接添加到主构建中,
add_subdirectory()就好像它是主项目自己的源代码的一部分一样。这无法通过传统的使用来完成,ExternalProject因为直到构建时才下载源代码。项目可以使用替代策略(例如 git 子模块)来克服这个问题,但它们也有自己的缺点。
For external projects that also use CMake as their build system, the
flexibility to build it with different settings to the main project is often
unnecessary. In fact, the more common case is likely to be that the external
project should be built with the same settings as the main project, but this is
not all that easy to do using ExternalProject. Often a much more convenient
arrangement would be to add it to the main build directly using
add_subdirectory() as though it was part of the main project’s own sources.
This cannot be done with the traditional use of ExternalProject because
the source isn’t downloaded until build time. Projects may use alternative
strategies such as git submodules to overcome this, but they are not without
their own drawbacks too.
CMake 3.11 中添加了该FetchContent模块来解决上述问题。它ExternalProject在内部使用来设置一个下载和更新外部内容的子构建,但它是在配置阶段执行的。这意味着下载的内容可以立即使用,因此主项目可以通过将其带入主构建
add_subdirectory(),将其用作资源等。
The FetchContent module was added in CMake 3.11 to solve problems like those
mentioned above. It uses ExternalProject internally to set up a sub-build
which downloads and updates the external content, but it does this during
the configure stage. This means the downloaded content is available
immediately, so the main project can then bring it into the main build via
add_subdirectory(), make use of it as resources and so on.
在依赖于许多外部项目的项目中,有时可能会出现这些外部项目依次共享一些共同依赖项的情况。多次下载和构建这些常见依赖项是不可取的,但ExternalProject它本身并不能直接提供处理该问题的方法。该FetchContent模块也为这种情况提供了解决方案。它允许与用于启动下载的命令分开定义外部项目的依赖项详细信息。第一次为给定依赖项指定下载详细信息时,它们会在内部保存,以后任何定义它们的尝试都会被默默忽略。当项目被要求填充依赖项时,它会使用这些保存的详细信息,并且项目的任何其他部分都可以简单地重新使用该内容,而无需再次下载它们。这种“第一个 setter 获胜”方法意味着父项目可以覆盖通过 拉入的外部子项目的依赖项详细信息
add_subdirectory()。
In projects that depend on many external projects, it can sometimes be the case
that those external projects in turn share some common dependencies. It would
be undesirable to download and build those common dependencies multiple times,
but ExternalProject on its own doesn’t directly provide a way to handle that.
The FetchContent module offers a solution to this scenario as well. It allows
dependency details of external projects to be defined separately from the
command that is used to initiate the download. The first time download details
are specified for a given dependency, they are saved internally and any later
attempts to define them are silently ignored. When the project is asked to
populate the dependency, it uses those saved details and any other parts of the
project can simply re-use that content instead of downloading them again. This
"first setter wins" approach means that a parent project can override
dependency details of external child projects pulled in via
add_subdirectory().
FetchContent以下示例演示了如何使用该模块的规范模式:
The canonical pattern for how the FetchContent module is intended to be used
is demonstrated by the following example:
include(FetchContent)
FetchContent_Declare(googletest ①
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG ec44c6c1675c25b9827aacd08c02433cccde7780
)
# NOTE: A more concise way of expressing the following is
# available from CMake 3.14, discussed further below
FetchContent_GetProperties(googletest) ②
if(NOT googletest_POPULATED)
FetchContent_Populate(googletest)
add_subdirectory(
${googletest_SOURCE_DIR}
${googletest_BINARY_DIR} ③
)
endif()include(FetchContent)
FetchContent_Declare(googletest ①
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG ec44c6c1675c25b9827aacd08c02433cccde7780
)
# NOTE: A more concise way of expressing the following is
# available from CMake 3.14, discussed further below
FetchContent_GetProperties(googletest) ②
if(NOT googletest_POPULATED)
FetchContent_Populate(googletest)
add_subdirectory(
${googletest_SOURCE_DIR}
${googletest_BINARY_DIR} ③
)
endif()
xxx_SOURCE_DIR和xxx_BINARY_DIR到
add_subdirectory()。当xxx_SOURCE_DIR指向不在当前二进制目录中的位置时(通常会发生这种情况),
add_subdirectory()还需要给出关联的二进制目录。xxx_SOURCE_DIR and xxx_BINARY_DIR to
add_subdirectory(). When xxx_SOURCE_DIR points to a location not in the
current binary directory (and this is typically what occurs),
add_subdirectory() requires the associated binary directory to be given
as well.该FetchContent_Declare()命令要求其第一个参数是所声明的依赖项的名称(该名称不区分大小写)。名称后面的参数应该是 所支持的任何选项ExternalProject_Add(),与配置、构建、安装或测试步骤相关的选项除外。实际上,通常给出的唯一选项是定义下载方法的选项,例如上面 GoogleTest 示例中的 git 详细信息。
The FetchContent_Declare() command requires its first argument to be the
name of the dependency being declared (this name is treated case
insensitively). The arguments that follow the name are expected to be any of
the options supported by ExternalProject_Add(), except those relating to the
configure, build, install or test steps. In practice, the only options
typically given are those that define a download method, such as the git
details in the GoogleTest example above.
该FetchContent_GetProperties()命令允许项目检查特定依赖项是否已填充,并且还检索一些目录详细信息。该命令的完整形式如下:
The FetchContent_GetProperties() command allows the project to check
whether a particular dependency has already been populated and it also retrieves
some directory details. The full form of the command is as follows:
FetchContent_GetProperties(name
[SOURCE_DIR srcDirVar]
[BINARY_DIR binDirVar]
[POPULATED doneVar]
)FetchContent_GetProperties(name
[SOURCE_DIR srcDirVar]
[BINARY_DIR binDirVar]
[POPULATED doneVar]
)
、SOURCE_DIR和选项可用于指定变量的名称,在该变量中存储依赖项的关联BINARY_DIR属性
。如果没有给出这些选项,则该命令设置变量,并且
在调用者的范围内,其中被
转换为小写。如果遵循规范模式,则不需要可选参数。POPULATEDname<lcname>_SOURCE_DIR<lcname>_BINARY_DIR<lcname>_POPULATED<lcname>name
The SOURCE_DIR, BINARY_DIR and POPULATED options can be used to specify
the name of a variable in which to store the associated property for the name
dependency. If none of these options are given, then the
command sets the variables <lcname>_SOURCE_DIR, <lcname>_BINARY_DIR and
<lcname>_POPULATED in the caller’s scope, where <lcname> is the name
converted to lowercase. The optional arguments are not needed if the canonical
pattern is being followed.
POPULATED如果FetchContent_Populate()已在项目中的某个位置为指定的name. 如果为 true,则该SOURCE_DIR属性指定在哪里可以找到下载的内容。FetchContent_Populate()由于下载的内容可能不是调用
位置的直接子目录,BINARY_DIR因此调用 时几乎总是需要该属性
add_subdirectory()。
The POPULATED property will be true if FetchContent_Populate() has
already been called somewhere in the project for the specified name. If it is
true, then the SOURCE_DIR property specifies where the downloaded contents
can be found. Since the downloaded contents might not be an immediate
subdirectory of the place where FetchContent_Populate() is called, the
BINARY_DIR property is almost always needed as well for the call to
add_subdirectory().
如果FetchContent_GetProperties()确认指定内容尚未填充,则FetchContent_Populate()可以调用该命令进行内容填充。当用作具有上面所示的规范形式的项目的一部分时,它将仅接受一个参数,即要填充的依赖项的名称。使用先前声明的已保存详细信息,如果先前运行尚未填充内容,则将填充该内容
cmake。、<lcname>_POPULATED和变量也将在调用者的作用域中设置,其方式<lcname>_SOURCE_DIR与
<lcname>_BINARY_DIR调用
FetchContent_GetProperties(name).
If FetchContent_GetProperties() has confirmed that the specified content has
not yet been populated, then the FetchContent_Populate() command can be
called to carry out the content population. When used as part of a project with
the canonical form shown above, it will accept just one argument, which is the
name of the dependency to populate. Using the saved details declared earlier,
the content is populated if it hasn’t already been populated by a previous
cmake run. The <lcname>_POPULATED, <lcname>_SOURCE_DIR and
<lcname>_BINARY_DIR variables will also be set in the caller’s scope in
exactly the same way as they are for a call to
FetchContent_GetProperties(name).
以下示例重点介绍了FetchContent模块允许顶级项目覆盖较低级别依赖项设置的详细信息的方式。TopProj考虑一个依赖于外部项目Foo
和的顶级项目Bar。两者Foo都Bar依赖于另一个外部项目,Jerry但它们各自想要的版本略有不同。
The following example highlights the way the FetchContent module allows a top
level project to override the details set by the lower level dependencies.
Consider a top level project TopProj which depends on external projects Foo
and Bar. Both Foo and Bar in turn both depend on another external
project, Jerry, but they each want slightly different versions of it.
Jerry只需下载并构建一份副本Foo即可Bar
使用。当这些项目合并到一个构建中时,所选版本必须覆盖或或什至两者通常Jerry使用的版本。顶级项目负责确保选择有效的版本,以便可以根据它进行构建。此示例假设虽然在自行构建时使用版本 1.3,但它可以安全地使用更高版本。所需的安排和实现它的示例如下所示:FooBarFooBarFoo
Only one copy of Jerry should be downloaded and built, which Foo and Bar
would then use. When these projects are combined into one build, the selected
version of Jerry has to override the version normally used by Foo or Bar,
or possibly even both. The top level project is responsible for ensuring that a
valid version is selected such that Foo and Bar can build against it. This
example assumes that while Foo uses version 1.3 when built on its own, it can
safely use a later version. The desired arrangement and an example that
implements it looks like this:
# Declare the direct dependencies
include(FetchContent)
FetchContent_Declare(foo GIT_REPOSITORY ... GIT_TAG ...)
FetchContent_Declare(bar GIT_REPOSITORY ... GIT_TAG ...)
# Override the Jerry dependency to force our preference
FetchContent_Declare(jerry
URL https://example.com/releases/jerry-1.5.tar.gz
URL_HASH ...
)
# Populate the direct dependencies but leave Jerry
# to be populated by foo
FetchContent_GetProperties(foo)
if(NOT foo_POPULATED)
FetchContent_Populate(foo)
add_subdirectory(
${foo_SOURCE_DIR}
${foo_BINARY_DIR}
)
endif()
FetchContent_GetProperties(bar)
if(NOT bar_POPULATED)
FetchContent_Populate(bar)
add_subdirectory(
${bar_SOURCE_DIR}
${bar_BINARY_DIR}
)
endif()# Declare the direct dependencies
include(FetchContent)
FetchContent_Declare(foo GIT_REPOSITORY ... GIT_TAG ...)
FetchContent_Declare(bar GIT_REPOSITORY ... GIT_TAG ...)
# Override the Jerry dependency to force our preference
FetchContent_Declare(jerry
URL https://example.com/releases/jerry-1.5.tar.gz
URL_HASH ...
)
# Populate the direct dependencies but leave Jerry
# to be populated by foo
FetchContent_GetProperties(foo)
if(NOT foo_POPULATED)
FetchContent_Populate(foo)
add_subdirectory(
${foo_SOURCE_DIR}
${foo_BINARY_DIR}
)
endif()
FetchContent_GetProperties(bar)
if(NOT bar_POPULATED)
FetchContent_Populate(bar)
add_subdirectory(
${bar_SOURCE_DIR}
${bar_BINARY_DIR}
)
endif()
include(FetchContent)
FetchContent_Declare(jerry
URL https://example.com/releases/jerry-1.3.tar.gz
URL_HASH ...
)
FetchContent_GetProperties(jerry)
if(NOT jerry_POPULATED)
FetchContent_Populate(jerry)
add_subdirectory(
${jerry_SOURCE_DIR}
${jerry_BINARY_DIR}
)
endif()include(FetchContent)
FetchContent_Declare(jerry
URL https://example.com/releases/jerry-1.3.tar.gz
URL_HASH ...
)
FetchContent_GetProperties(jerry)
if(NOT jerry_POPULATED)
FetchContent_Populate(jerry)
add_subdirectory(
${jerry_SOURCE_DIR}
${jerry_BINARY_DIR}
)
endif()
CMakeLists.txt的文件与Bar的文件相同,Foo只是URL会指定jerry-1.5.tar.gz而不是jerry-1.3.tar.gz。上面的框架示例允许Foo和Bar单独构建为独立项目,或者它们可以合并到另一个项目中,
TopProj并且仍然具有解决常见依赖关系所需的灵活性。
The CMakeLists.txt file for Bar would be identical to that of Foo except
the URL would specify jerry-1.5.tar.gz instead of jerry-1.3.tar.gz. The
above skeleton example allows Foo and Bar to be built as standalone
projects on their own, or they can be incorporated into another project like
TopProj and still have the required flexibility to resolve the common
dependencies.
规范模式可能有点冗长,需要许多命令来测试依赖项是否已填充,如果尚未填充,则进行填充,然后将其添加到主构建中。如果添加许多依赖项,这可能会非常乏味且重复。使用 CMake 3.14 或更高版本,可以使用另一个名为 的命令更简洁地实现规范模式FetchContent_MakeAvailable()。它接受依赖项名称列表,这些依赖项名称应该根据规范模式填充并添加到主构建中,跳过任何已填充的名称。CMakeLists.txt作为附加增强功能,如果填充了依赖项但其源树顶部没有
文件,add_subdirectory()则不会调用该依赖项,也不会将其视为错误。这允许使用该命令填充任意项目,这些项目可能只包含用作资源、脚本、工具链文件等的文件集合。使用 CMake 3.18 或更高版本,SOURCE_SUBDIR可以提供该选项
FetchContent_Declare()来指定替代位置搜索
CMakeLists.txt文件而不是依赖项源树的顶部。
The canonical pattern can be a little verbose, requiring a number of commands
to test whether a dependency has already been populated, do the population if
it hasn’t been done yet and then add it to the main build.
If many dependencies are being added, this can be quite tedious and repetitive.
With CMake 3.14 or later, the canonical pattern can be implemented much more
concisely using another command called FetchContent_MakeAvailable().
It accepts a list of dependency names which should be populated and added
to the main build according to the canonical pattern, skipping over any that
have already been populated.
As an additional enhancement, if a dependency is populated but there is no
CMakeLists.txt file at the top of its source tree, add_subdirectory() will
not be called and it will not be considered an error.
This allows the command to be used to populate arbitrary projects which may
simply contain a collection of files to be used as resources, scripts,
toolchain files, etc.
With CMake 3.18 or later, the SOURCE_SUBDIR option can be given with
FetchContent_Declare() to specify an alternative location to search for a
CMakeLists.txt file instead of the top of the dependency’s source tree.
TopProj考虑前面涉及、Foo和Bar的示例Jerry。如果TopProj想要完全接管该方式Foo,Bar并Jerry
填充并添加到主构建中,则可以非常简洁地实现,如下所示(为简洁起见,省略了参数值):
Consider the earlier example involving TopProj, Foo, Bar and Jerry.
If TopProj wanted to completely take over the way Foo, Bar and Jerry
were populated and added to the main build, it could be achieved very concisely
like so (argument values have been omitted for brevity):
# Declare the direct dependencies
include(FetchContent)
FetchContent_Declare(jerry URL ... URL_HASH ...)
FetchContent_Declare(foo GIT_REPOSITORY ... GIT_TAG ...)
FetchContent_Declare(bar GIT_REPOSITORY ... GIT_TAG ...)
# Apply the canonical pattern to each dependency in turn.
# Because jerry is first in the list, it will be populated
# here rather than by either foo or bar.
FetchContent_MakeAvailable(jerry foo bar)# Declare the direct dependencies
include(FetchContent)
FetchContent_Declare(jerry URL ... URL_HASH ...)
FetchContent_Declare(foo GIT_REPOSITORY ... GIT_TAG ...)
FetchContent_Declare(bar GIT_REPOSITORY ... GIT_TAG ...)
# Apply the canonical pattern to each dependency in turn.
# Because jerry is first in the list, it will be populated
# here rather than by either foo or bar.
FetchContent_MakeAvailable(jerry foo bar)
显然,这比手动为每个依赖项手动编写规范模式要简单得多。对于可以将 CMake 3.14 或更高版本设置为其最低 CMake 版本的项目,
FetchContent_MakeAvailable()应优先考虑,除非需要超出规范模式的额外自定义(请参阅
第 29.2 节“非超级构建结构”中对此的进一步讨论)。
Clearly this is much simpler than manually writing out the canonical pattern
for each dependency.
For projects that can set CMake 3.14 or later as their minimum CMake version,
FetchContent_MakeAvailable() should be preferred unless extra customization
beyond the canonical pattern is needed (see further discussion of this in
Section 29.2, “Non-superbuild Structure”).
有时,开发人员可能希望同时处理多个项目,对主项目及其依赖项或跨多个依赖项进行更改等。在更改外部项目的部分内容时,开发人员将希望与本地项目一起工作复制,而不必更新每次下载的远程位置。该FetchContent模块允许使用 CMake 缓存变量覆盖任何外部依赖项的源目录,从而为这种操作模式提供直接支持。这些变量的名称格式
FETCHCONTENT_SOURCE_DIR_<DEPNAME>如下,其中<DEPNAME>是大写的依赖项名称。
There may be occasions when a developer wants to work on multiple projects at
once, making changes in both the main project and its dependencies or across
multiple dependencies, etc. When changing parts of an external project, the
developer will want to work with a local copy rather than have to update the
remote location it is downloaded from each time. The FetchContent module
offers direct support for this mode of operation by allowing the source
directory of any external dependency to be overridden with a CMake cache
variable. These variables have names of the form
FETCHCONTENT_SOURCE_DIR_<DEPNAME> where <DEPNAME> is the dependency name in
uppercase.
在前面的示例中,考虑开发人员想要进行更改Foo并查看它如何影响主项目的情况。Foo他们可以在主项目外部
创建一个单独的克隆,然后设置FETCHCONTENT_SOURCE_DIR_FOO到该位置。该TopProj项目将使用该本地副本的源,并且不会以任何方式修改它,但它仍然会在自己的TopProj构建区域内使用相同的构建目录。唯一的区别是源来自哪里,通过设置
FETCHCONTENT_SOURCE_DIR_FOO,开发人员将接管对内容的控制。他们可以自由地更改本地副本中的任何内容、进行进一步的提交、切换分支或任何其他可能需要的内容,然后重建主项目,而TopProj无需进行TopProj任何更改。
In the previous example, consider a situation where the developer wants to make
a change to Foo and see how it affects the main project. They can create a
separate clone of Foo outside of the main project and then set
FETCHCONTENT_SOURCE_DIR_FOO to that location. The TopProj project would use
the source of that local copy and not modify it in any way, but it would still
use the same build directory for it within its own TopProj build area. The
only difference would be where the source comes from and by setting
FETCHCONTENT_SOURCE_DIR_FOO, the developer would take over control of the
content. They would be free to change anything in their local copy, make
further commits, switch branches or whatever else may be needed, then rebuild
the main TopProj project without having to change TopProj at all.
适合上述用途的一种安排是有一个公共目录,开发人员可以在该目录下检查他们想要使用的不同项目。然后,主项目可以在需要时指向这些本地结帐,但否则仍使用默认下载的内容。对于上面的示例,这种安排可能如下所示:
An arrangement that works well for the above usage is to have a common directory under which the developer checks out the different projects they want to work with. The main project can then be pointed at these local checkouts when needed, but still use the default downloaded contents otherwise. Such an arrangement may look like this for the above example:
如果开发人员想要对其进行一些更改Foo并使用 的构建对其进行测试TopProj,他们可以设置FETCHCONTENT_SOURCE_DIR_FOO为
/…/Projects/Foo,但依赖项的所有构建输出Foo仍将位于Projects/builds/TopProj-debug. 如果
FETCHCONTENT_SOURCE_DIR_BAR未设置,则Bar仍会下载,而不是使用 中的本地结帐Projects/Bar。FETCHCONTENT_SOURCE_DIR_BAR开发人员可以随时通过设置轻松切换到本地结账
。
If the developer wanted to make some changes to Foo and test it with a build
of TopProj, they could set FETCHCONTENT_SOURCE_DIR_FOO to
/…/Projects/Foo, but all of the build output from the Foo dependency
would still be under Projects/builds/TopProj-debug. If
FETCHCONTENT_SOURCE_DIR_BAR was left unset, then Bar would still be
downloaded rather than using the local checkout in Projects/Bar. The
developer could switch to that local checkout just as easily by setting
FETCHCONTENT_SOURCE_DIR_BAR at any time.
由于相关的缓存变量都共享相同的前缀,因此很容易在 CMake GUI 或工具中找到它们ccmake。这反过来又使得查看哪些项目当前正在使用本地副本而不是默认下载的内容变得微不足道。
Because the relevant cache variables all share the same prefix, they are easy
to find in the CMake GUI or ccmake tool.
This in turn makes it trivial to see which projects are currently using a
local copy instead of the default downloaded contents.
上述场景的一个显着优点是它与代码重构工具等 IDE 功能集成良好。IDE 可以查看整个项目,包括其依赖项,因此当使用这些依赖项的本地检出时,可以跨多个项目透明地执行重构。项目就像它们都是同一个项目的一部分一样容易。即使不使用任何依赖项的本地检查,IDE 也有更大的机会构建更完整的代码模型,用于自动完成、跟随符号等。
A significant advantage of the above scenario is that it integrates well with IDE features like code refactoring tools, etc. The IDE sees the whole project, including its dependencies, so when local checkouts of those dependencies are used, refactoring can be performed transparently across multiple projects just as easily as if they were all part of the same project. Even if not using any local checkouts of dependencies, the IDE has greater opportunity to build up a more complete code model for auto completion, following symbols and so on.
FetchContent不仅仅可以下载外部项目的源代码并通过add_subdirectory(). 另一个用例是在中央存储库中收集常用的 CMake 模块,并在许多项目中重复使用它们。可以通过此机制引入多个集合,这使得合并来自其他项目的有用 CMake 脚本相对简单,而无需在主项目自己的源代码中嵌入副本。cmake下面演示了下载外部 git 存储库并将其子目录添加到主项目的 CMake 模块搜索路径中的示例。
FetchContent enables more than just downloading an external project’s source
code and adding it to the main project via add_subdirectory(). Another use
case is to collect commonly used CMake modules in a central repository and
re-use them across many projects. Multiple collections can be pulled in via
this mechanism, which makes it relatively straightforward to incorporate useful
CMake scripts from other projects without having to embed copies in the main
project’s own sources. The following demonstrates an example where an external
git repository is downloaded and its cmake subdirectory is added to the CMake
module search path of the main project.
include(FetchContent)
FetchContent_Declare(JoeSmithUtils
GIT_REPOSITORY ... GIT_TAG ...
)
FetchContent_GetProperties(JoeSmithUtils)
if(NOT joesmithutils_POPULATED)
FetchContent_Populate(JoeSmithUtils)
list(APPEND CMAKE_MODULE_PATH
${joesmithutils_SOURCE_DIR}/cmake
)
endif()include(FetchContent)
FetchContent_Declare(JoeSmithUtils
GIT_REPOSITORY ... GIT_TAG ...
)
FetchContent_GetProperties(JoeSmithUtils)
if(NOT joesmithutils_POPULATED)
FetchContent_Populate(JoeSmithUtils)
list(APPEND CMAKE_MODULE_PATH
${joesmithutils_SOURCE_DIR}/cmake
)
endif()
另一种情况利用了该FetchContent模块甚至可以在第一次调用之前使用的事实project()。此功能允许模块提供工具链文件,开发人员可以将其用于主项目。
Another scenario takes advantage of the fact that the FetchContent module can
be used even before the first project() call. This feature allows the module
to provide toolchain files which the developer can then use for the main
project.
cmake_minimum_required(VERSION 3.14)
include(FetchContent)
FetchContent_Declare(CompanyXToolchains
GIT_REPOSITORY ...
GIT_TAG ...
SOURCE_DIR ${CMAKE_BINARY_DIR}/toolchains
)
FetchContent_MakeAvailable(CompanyXToolchains)
project(MyProj)cmake_minimum_required(VERSION 3.14)
include(FetchContent)
FetchContent_Declare(CompanyXToolchains
GIT_REPOSITORY ...
GIT_TAG ...
SOURCE_DIR ${CMAKE_BINARY_DIR}/toolchains
)
FetchContent_MakeAvailable(CompanyXToolchains)
project(MyProj)
cmake -DCMAKE_TOOLCHAIN_FILE=toolchains/beta_cxx.cmake ...
cmake -DCMAKE_TOOLCHAIN_FILE=toolchains/beta_cxx.cmake ...
在上面的示例中,使用该选项显式覆盖工具链下载到的目录SOURCE_DIR。假设该
CompanyXToolchains项目只是工具链文件的简单集合,没有子目录,这使得它们的位置既可预测又易于开发人员使用。如果组织使用非常特定的工具链,并且这些工具链预计始终安装在同一位置,那么这可能是促进整个团队使用通用构建设置的非常有效的方法。该技术甚至可以扩展到下载实际的工具链本身。
In the above example, the directory into which the toolchains are downloaded is
explicitly overridden using the SOURCE_DIR option. Assuming the
CompanyXToolchains project is just a simple collection of toolchain files
with no subdirectories, this makes their location both predictable and easy for
developers to use. Where organizations work with very specific toolchains that
are expected to always be installed to the same place, this can be a very
effective way to facilitate a whole team using common build setups. The
technique could even be extended to download the actual toolchains themselves.
在大多数情况下,该FetchContent模块具有一些强大的优势,但也有一些限制需要注意。主要限制是 CMake 目标名称在组合的整个项目集中必须是唯一的,因此如果两个外部项目定义了具有相同名称的目标,则它们不能同时通过add_subdirectory(). 如果项目遵循使用项目特定前缀或类似内容的命名约定,则很容易避免此限制。困难往往来自于从未期望以这种方式使用的项目,以及使用可能很常见的相当通用的名称的项目。对于那些使用项目特定目标名称的项目,仍然可以使用OUTPUT_NAME目标属性单独控制所创建的二进制文件的名称。例如:
For the most part, the FetchContent module comes with some strong advantages,
but there are some restrictions to be aware of. The main limitation is that
CMake target names must be unique across the whole set of projects being
combined, so if two external projects define a target with the same name, they
cannot both be added via add_subdirectory(). If projects are following a
naming convention that uses a project specific prefix or something similar,
then this limitation is fairly easy to avoid. The difficulties tend to come
from projects which never expected to be used in this way and which use fairly
generic names that are likely to be commonplace. For those projects that do
use project specific target names, the name of the binary that is created can
still be controlled separately using the OUTPUT_NAME target property. For
example:
add_library(BagOfBeans_Varieties ...)
set_target_properties(BagOfBeans_Varieties PROPERTIES
OUTPUT_NAME BeanTypes
)
add_executable(BagOfBeans_Planter )
set_target_properties(BagOfBeans_Planter PROPERTIES
OUTPUT_NAME Planter
)add_library(BagOfBeans_Varieties ...)
set_target_properties(BagOfBeans_Varieties PROPERTIES
OUTPUT_NAME BeanTypes
)
add_executable(BagOfBeans_Planter )
set_target_properties(BagOfBeans_Planter PROPERTIES
OUTPUT_NAME Planter
)
OUTPUT_NAME第 29.5.2 节“目标输出”中更详细地介绍了相关属性
。
OUTPUT_NAME and related properties are covered in more detail in
Section 29.5.2, “Target Outputs”.
类似但稍微不太严格的限制适用于安装组件。理想情况下,每个项目都会使用项目特定的前缀或同样唯一的名称来命名其安装组件。这使得父项目可以只挑选出它想要包含在自己的打包中的组件。如果两个或多个外部项目依赖项使用相同的安装组件名称,则父项目无法将它们分开,而必须将它们视为一个。这是否重要取决于具体情况,但通过确保项目对其安装组件使用良好的命名约定,可以轻松避免这种情况。
A similar but slightly less severe limitation applies for install components. Ideally, each project would name their install components with a project specific prefix or something equally unique. This allows a parent project to pick out just the components it wants to include in its own packaging. If two or more external project dependencies use the same install component names, then the parent project cannot separate them and has to treat them as one. Whether this matters or not will be situation dependent, but again it can be easily avoided by ensuring projects use good naming conventions for their install components.
通过将外部依赖吸收到更大的父构建中的做法add_subdirectory()还没有那么普遍。许多项目从未考虑过该用例,并且遇到使项目难以以这种方式合并的模式并不罕见。一个常见的例子是,一个项目假设它是顶级项目,并且它使用像
CMAKE_SOURCE_DIR和 之类的变量,而CMAKE_BINARY_DIR像
CMAKE_CURRENT_SOURCE_DIR和 之类的替代变量CMAKE_CURRENT_BINARY_DIR可能更合适。此类问题通常很容易修复,但需要对项目进行写访问、项目维护人员接受更改、提供可以进行相关修复的项目副本或其他类似措施。
The practice of absorbing an external dependency into a larger parent build
via add_subdirectory() is not yet all that widespread. Many projects have
never considered that use case and it is not unusual to encounter patterns that
make a project hard to incorporate in this way. A common example is where a
project assumes it is the top level project and it uses variables like
CMAKE_SOURCE_DIR and CMAKE_BINARY_DIR where alternatives like
CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_BINARY_DIR may be more
appropriate. Such problems are usually easy to fix, but it requires write
access to the project, having changes accepted by project maintainers,
vendoring a copy of the project in which the relevant fix can be made or other
similar measures.
另一个名为 的模块ExternalData提供了另一种管理构建时下载的文件的方法。该模块的重点是在构建代表该数据的特定目标时下载测试数据。在某些方面,它的工作原理类似ExternalProject,但两个模块定义要下载的内容的方式有很大不同。该
ExternalProject模块允许显式定义下载详细信息,并且支持多种方法。该ExternalData模块采用不同的方法,期望单个文件在一组项目定义的基本 URL 位置之一下可用,并使用特定的哈希方法编码路径和文件名。实际文件在项目的源代码树中由同名的占位符文件表示,但散列算法的名称附加为文件名后缀。该模块提供了一个函数,可将特殊形式的字符串参数转换为最终下载的位置和名称,并提供函数的包装器,add_test()
以便更轻松地将这些解析的位置传递给测试命令。
Another module called ExternalData provides an alternative way of
managing files to be downloaded at build time. The focus of this module is on
downloading test data when a particular target representing that data is built.
In some ways, it is similar to how ExternalProject works, but the way the two
modules define the content to be downloaded is considerably different. The
ExternalProject module allows the download details to be explicitly defined
and it supports a variety of methods. The ExternalData module takes a
different approach, expecting individual files to be available under one of a
set of project-defined base URL locations, with paths and file names encoded
using a particular hashing method. The actual file is represented in the
project’s source tree by a placeholder file of the same name, except with the
name of a hashing algorithm appended as a file name suffix. The module provides
a function to translate string arguments of a special form into their final
downloaded location and name, along with a wrapper around the add_test()
function to make it easier to pass these resolved locations to test commands.
在实践中,建立必要支持所涉及的步骤
ExternalData往往会降低其吸引力。要下载数据的服务器必须使用定义的结构并单独处理每个文件。每次添加新文件或更新现有文件时,都必须手动对其进行哈希处理并上传到与该哈希值匹配的路径和文件名。如果文件很大但与前一次迭代只有很小的差异,则仍然需要完全复制该文件。相比之下,该
ExternalProject模块可以使用其基于存储库的下载方法之一实现相同的功能,但所涉及的步骤对于大多数开发人员来说都是简单且熟悉的。这些方法通常还可以有效地处理大型文本文件中的小更改。
In practice, the steps involved in setting up the necessary support for
ExternalData tend to make it less attractive. The server from which the data
is to be downloaded has to use a defined structure and treats every file
separately. Every time a new file is added or an existing file is updated, it
has to be manually hashed and uploaded to a path and file name that matches
that hash. If the file is large but has only a small difference to the previous
iteration, the file still has to be fully copied. In comparison, the
ExternalProject module can achieve the same thing with one of its repository
based download methods, but the steps involved are easy and familiar for most
developers. These methods also typically handle small changes in large textual
files efficiently.
考虑使用的一个原因ExternalData是它支持文件系列而不仅仅是单个文件。这更像是一个小众场景,通常出现在处理一系列文件的测试中。即使如此,也可以使用循环来实现类似的功能ExternalProject,
foreach()这可能仍然比相当严格的结构所需的设置更简单ExternalData。如果项目的测试主要关注时间序列数据或其他类似的顺序数据集,那么至少值得评估是否ExternalData提供了一种在构建时按需获取数据的更好方法。请查阅模块的文档以获取参考详细信息,或者了解更实用的介绍,
与本书位于同一站点的有关此主题的文章可能会有所帮助。
One reason to consider using ExternalData is its support for a file series
rather than just an individual file. This is more of a niche scenario that
typically arises for tests that process a sequence of files. Even then, one
could potentially implement similar functionality with ExternalProject and a
foreach() loop, which may still be simpler to set up than the fairly rigid
structure ExternalData requires. If the project has tests that are heavily
focused on time series data or other similarly sequential data sets, then it
may be worth at least evaluating whether ExternalData offers a preferable way
to obtain that data on demand at build time. Consult the module’s documentation
for reference details, or for a more practical introduction, the
article
on this topic available from the same site as this book may be helpful.
和ExternalProject都FetchContent提供了将外部内容合并到父项目中的方法。ExternalProject适合引入成熟的外部项目,具有良好的打包并提供定义良好的配置文件,find_package()可用于导入相关目标。它还具有以下优点:只有在构建需要外部依赖项时才会下载外部依赖项,并且下载可以与其他构建任务并行完成。当开发人员需要跨多个项目工作并进行更改时,尤其是涉及少量重构时,这可能不太方便。
Both ExternalProject and FetchContent provide ways to incorporate external
content into a parent project. ExternalProject is good for bringing in
external projects that are mature, have good packaging and provide well defined
config files that find_package() can use to import the relevant targets. It
also has the advantage that external dependencies are only downloaded if the
build needs them and the downloading can be done in parallel with other build
tasks. It can be less convenient when developers need to work across multiple
projects and make changes, especially if any modest amount of refactoring is
involved.
由于ExternalProject很长一段时间以来它一直是 CMake 的一部分,因此在线上也有一套可用的材料。尽管如此,开发人员却常常难以稳健地设置它。ExternalProject一个特别常见的弱点是以平台特定的方式硬编码库的路径和文件名,这是与主项目中的其他目标混合而不是经典的超级构建安排的结果。在选择使用之前,请仔细考虑外部依赖项的打包成熟度和质量以及主项目是否可以使用超级构建安排ExternalProject。如果主项目无法转换为超级构建安排,最好不要使用它。
Since ExternalProject has been part of CMake for a long time, there
is also an established body of material available for it online.
Despite this, it is common to see developers struggle with getting it set up
robustly.
A particularly common weakness is hard-coding paths and file names of libraries
in platform specific ways as a result of blending ExternalProject with other
targets in the main project instead of a classical superbuild arrangement.
Give careful thought to the maturity and quality of packaging of the external
dependencies and whether the main project can use a superbuild arrangement
before choosing to make use of ExternalProject.
Prefer not to use it if the main project cannot be converted to a superbuild
arrangement.
FetchContent当其他项目需要以允许同时处理的方式添加到构建中时,该模块是一个不错的选择。它为开发人员提供了跨项目工作的自由,并以无缝方式临时切换到本地结帐、更改分支、测试不同的发行版本和各种其他用例。它对 IDE 工具也很友好,因为整个构建显示为单个项目。代码完成通常可以提供更深入的了解,并且可能比单独加载项目更可靠。还可以更稳健地跨存储库执行重构。
The FetchContent module is a good choice where other projects need to be
added to the build in a way that allows them to be worked on at the same time.
It affords developers the freedom to work across projects and temporarily
switch to local checkouts, change branches, test with different release
versions and various other use cases in a seamless manner.
It is also friendly to IDE tools, since the whole build appears as a single
project.
Code completion often provides greater insight and may be more reliable than if
the projects had been loaded separately.
Refactoring can also be performed across repositories much more robustly.
如果向现有的成熟项目添加依赖项,那么FetchContent破坏性可能会比 低得多ExternalProject,因为它不需要对主项目进行任何重组。它还非常适合合并相对不成熟且尚未实施安装组件和打包的外部项目。另一个优点FetchContent是它本质上导致在整个项目层次结构中使用相同的编译器和设置。如果可以接受最低 CMake 版本 3.11 或更高版本,请考虑是否
FetchContent比
ExternalProject. 还强烈建议熟悉ccache加速构建等工具,因为使用
FetchContent. 有关如何为各种编译器进行设置的详细讨论,请参见第 30.3 节“编译器缓存” 。
If adding dependencies to an existing mature project, FetchContent can be
much less disruptive than ExternalProject, since it doesn’t require any
restructuring of the main project.
It is also well suited to incorporating external projects that are relatively
immature and which don’t yet have install components and packaging implemented.
A further advantage of FetchContent is that it inherently results in using
the same compiler and settings across the whole project hierarchy.
If a minimum CMake version of 3.11 or higher is acceptable, consider whether
FetchContent is a more convenient and natural fit for the project than
ExternalProject.
It is also strongly recommended to become familiar with tools like ccache for
speeding up the build, as the benefits are especially pronounced when using
FetchContent. See Section 30.3, “Compiler Caches” for a detailed discussion of how to set
this up for various compilers.
无论使用ExternalProject或FetchContent,如果为 git 存储库定义下载详细信息,最好设置GIT_TAG为提交哈希而不是分支或标记名称。这更有效,因为如果本地克隆已经有该提交,它可以避免建立任何网络连接。
Whether using ExternalProject or FetchContent, if download details are
being defined for a git repository, prefer to set GIT_TAG to the commit hash
rather than a branch or tag name. This is more efficient, since it avoids
making any network connection if the local clone already has that commit.
如果项目想要按需下载测试数据,请检查该
ExternalData模块是否是合适的选择。该ExternalProject模块可能更易于使用,并且可能会被大多数开发人员更好地理解,但在特定情况下(例如处理文件序列)ExternalData可能会更简单。如果有疑问,更喜欢ExternalProject它更简单的界面,并且可能更有效地处理大数据集的小变化。
If the project wants to download test data on demand, check whether the
ExternalData module is an appropriate choice. The ExternalProject module
may be simpler to use and is likely to be better understood by most developers,
but in specific cases such as working with file sequences, ExternalData could
potentially be simpler. If in doubt, prefer ExternalProject for its easier
interface and potentially more efficient handling of small changes to large
data sets.
当处理一个项目时,总是假设有一天它会被用作其他父项目的子项目。这为项目未来的使用方式提供了最大的灵活性。需要注意的常见问题包括:
When working on a project, always assume it will some day be used as a child of some other parent project. This provides the most flexibility for how the project can be used in the future. Common problems to look out for include:
CMAKE_CURRENT_SOURCE_DIRand这样的变量CMAKE_CURRENT_BINARY_DIR,而不是
CMAKE_SOURCE_DIRand 。CMAKE_BINARY_DIR
CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_BINARY_DIR rather than
CMAKE_SOURCE_DIR and CMAKE_BINARY_DIR when referring to locations
relative to the project’s own directory structure.
MyProj::mpfoo不仅仅是链接mpfoo)。这使得该项目可以在ExternalProject
和FetchContent场景中使用。
MyProj::mpfoo rather than
just mpfoo). This allows the project to be used in both ExternalProject
and FetchContent scenarios.
有助于建立有效的项目结构的因素是多种多样的。对一个项目有效的方法可能不适用于另一个项目,但通常有一些事情是常见的。在项目生命周期的早期选择灵活但可预测的目录结构可以使其以最小的摩擦和重组进行发展。
The factors that contribute to an effective project structure are many and varied. What works for one project may not work for another, but there are typically some things that do tend to be common. Choosing a flexible but predictable directory structure early in the life of a project allows it to evolve with minimal friction and reorganization.
最重要的决定之一是项目应该构建为超级建筑还是常规项目。两者有着根本的不同,各有优缺点。该决定很大程度上取决于项目希望如何处理其依赖项,以及是否有意愿和机会直接吸收它们或将它们隔离在自己的子构建中。对于那些没有任何依赖项的项目(重要的是,未来没有任何依赖项的前景),常规项目是显而易见的选择。但是,当存在依赖性时,正确的项目结构可能是对抗构建和使其顺利运行之间的区别。
One of the most important decisions is whether a project should be structured as a superbuild or as a regular project. The two are fundamentally different and have their own strengths and weaknesses. The decision largely comes down to how the project wants to treat its dependencies and whether there is a desire and opportunity to absorb them directly or keep them isolated in their own sub-builds. For those projects without any dependencies (and importantly without any future prospect of ever having any dependencies), a regular project is the obvious choice. But when there are dependencies, the right project structure can be the difference between fighting against the build and having it work smoothly.
邮件列表、问题跟踪器和问答网站上最常见的主题之一涉及由于尝试使用一种项目结构但期望它具有另一种项目结构的功能而产生的问题。在许多情况下,出现这种情况是因为项目是从特定结构开始的,但随着依赖项的添加,该结构不再支持开发人员希望项目能够执行的操作。相关人员已经习惯了使用现有的结构,因此改变它可能会造成很大的破坏,并且常常会遇到相当大的阻力。项目越老,这种改变可能就越困难。因此,在项目生命周期的早期决定如何处理依赖关系,并适当考虑未来的期望。
One of the most common topics that comes up on mailing lists, issue trackers and Q&A sites relates to problems stemming from trying to use one project structure but expecting it to have the capabilities of another. In many cases, this arises because a project is started with a particular structure, but then as dependencies are added, that structure no longer supports what the developer wants the project to be able to do. Those involved have become accustomed to working with the existing structure, so changing it will likely be very disruptive and will often meet with considerable resistance. The older a project is, the harder such a change is likely be. Therefore, decide how dependencies should be handled early in the life of the project, with due consideration for future expectations.
如果依赖项不使用 CMake 作为其构建系统,则超级构建往往是首选结构。这将每个依赖项视为其自己的单独构建,主项目指导整体顺序以及详细信息从一个依赖项的构建传递到另一个依赖项的方式。每个单独的构建都使用添加到主构建中ExternalProject。这种安排允许 CMake 查看每个构建生成的内容,并自动检测可以传递给其他依赖项的信息,从而避免在主构建中手动对该信息进行硬编码。即使所有依赖项都使用 CMake,出于其他原因,超级构建可能仍然是首选,例如避免目标名称冲突或假设它们始终是顶级项目的项目出现问题。
Where dependencies do not use CMake as their build system, a superbuild tends
to be the preferred structure. This treats each dependency as its own separate
build, with the main project directing the overall sequence and the way details
are passed from one dependency’s build to another. Each separate build is added
to the main build using ExternalProject. Such an arrangement allows
CMake to look at what each build produces and automatically detect information
that can then be passed on to other dependencies, thereby avoiding having to
manually hard code that information in the main build. Even if all the
dependencies use CMake, a superbuild may still be preferred for other reasons,
such as to avoid target name clashes or problems with projects that assume they
are always the top level project.
超级构建允许精确控制单独依赖项构建的顺序。例如,在其他依赖项运行它们自己的配置阶段之前,可能需要一个或多个依赖项完全完成它们自己的构建,包括它们的安装步骤。对于这样的示例,后续配置步骤可以查看已安装的工件并自动计算出适当的文件名、位置等。这在常规构建中是不可能的。
A superbuild allows precise control over the sequencing of the separate dependency builds. For example, one or more dependencies can be required to fully complete their own build, including their install step, before other dependencies run their own configuration phase. For such an example, the later configuration steps can see the installed artifacts and work out the appropriate file names, locations, etc. automatically. This is not possible in a regular build.
CMakeLists.txt超级构建可以使用遵循相当可预测的模式的顶级文件来实现。一种变体对所有依赖项使用公共安装区域,而另一种变体将每个依赖项安装到它们自己的单独安装区域。虽然两者很相似,但使用公共安装区域的定义稍微简单一些:
Superbuilds can be implemented with a top level CMakeLists.txt file that
follows a fairly predictable pattern. One variation uses a common install area
for all dependencies, while another installs each dependency to their own
separate install area. While both are similar, using a common install area is
slightly simpler to define:
cmake_minimum_required(VERSION 3.0)
project(SuperbuildExample)
include(ExternalProject)
set(installDir ${CMAKE_CURRENT_BINARY_DIR}/install)
ExternalProject_Add(someDep1 ①
...
INSTALL_DIR ${installDir}
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)
ExternalProject_Add(someDep2
...
INSTALL_DIR ${installDir}
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
-DCMAKE_PREFIX_PATH:PATH=<INSTALL_DIR> ②
)
ExternalProject_Add_StepDependencies( ③
someDep2 configure someDep1
)cmake_minimum_required(VERSION 3.0)
project(SuperbuildExample)
include(ExternalProject)
set(installDir ${CMAKE_CURRENT_BINARY_DIR}/install)
ExternalProject_Add(someDep1 ①
...
INSTALL_DIR ${installDir}
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)
ExternalProject_Add(someDep2
...
INSTALL_DIR ${installDir}
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
-DCMAKE_PREFIX_PATH:PATH=<INSTALL_DIR> ②
)
ExternalProject_Add_StepDependencies( ③
someDep2 configure someDep1
)
find_package()对于用于查找其依赖项的其他依赖项,设置CMAKE_PREFIX_PATH为公共安装目录通常就足够了。find_package() to locate their
dependencies, setting CMAKE_PREFIX_PATH to the common install directory
is typically enough.如果每个依赖项都应安装到其自己的安装区域,则与上述唯一的区别是,CMAKE_PREFIX_PATH给后面的依赖项可能需要是所有先前依赖项的安装目录的列表,而不仅仅是单个公共安装目录。
If each dependency should be installed to its own install area, the only
difference to the above is that the CMAKE_PREFIX_PATH given to later
dependencies may need to be a list of all previous dependencies’ install
directories instead of just a single common install directory.
如果依赖项不使用 CMake 作为其构建系统,则整体结构不会改变,只会改变依赖项的构建详细信息的定义方式。例如,使用像 autotools 这样的构建系统的依赖项可能会像这样指定:
If a dependency doesn’t use CMake as its build system, the overall structure doesn’t change, only the way the dependency’s build details are defined. For instance, a dependency that uses a build system like autotools might instead be specified like so:
ExternalProject_Add(someDep3
INSTALL_DIR
${installDir}
CONFIGURE_COMMAND
<SOURCE_DIR>/configure --prefix <INSTALL_DIR>
...
)ExternalProject_Add(someDep3
INSTALL_DIR
${installDir}
CONFIGURE_COMMAND
<SOURCE_DIR>/configure --prefix <INSTALL_DIR>
...
)
其他选项可能还需要传递给这样的configure脚本,以更具体的方式告诉它在哪里可以找到其依赖项。这显然会根据依赖项的配置功能而有所不同。
Other options might also need to be passed to such a configure script to tell
it in more specific ways where to find its dependencies. This will obviously
vary based on the dependency’s configuration capabilities.
超级建筑中的包装不太简单。在某些方面,每个依赖项实际上都控制着自己的打包,因此顶级项目最终不太可能打包任何东西。相反,ExternalProject_Add()如果确实需要支持打包,则可能会为一个或多个调用提供自定义打包步骤。上一章演示了如何使用ExternalProject_Add_Step()如下函数实现此功能(类似的方法可用于非 CMake 子项目):
Packaging is a little less straightforward in superbuilds. In some respects,
each dependency is really in control of its own packaging, so the top level
project is ultimately unlikely to be packaging anything. Instead, one or more
of the ExternalProject_Add() calls is likely to be given a custom packaging
step, if indeed packaging needs to be supported at all. The previous chapter
demonstrated how to implement this with ExternalProject_Add_Step() function
like so (a similar approach can be used for non-CMake sub-projects):
ExternalProject_Add_Step(MyProj package
COMMAND ${CMAKE_COMMAND}
--build <BINARY_DIR>
--target package
DEPENDEES build
ALWAYS YES
EXCLUDE_FROM_MAIN YES
)
ExternalProject_Add_StepTargets(MyProj package)ExternalProject_Add_Step(MyProj package
COMMAND ${CMAKE_COMMAND}
--build <BINARY_DIR>
--target package
DEPENDEES build
ALWAYS YES
EXCLUDE_FROM_MAIN YES
)
ExternalProject_Add_StepTargets(MyProj package)
一般来说,要记住的关键一点是,当超级构建所做的只是将其他外部项目整合在一起时,它们就能很好地工作。它们通常依赖于具有明确定义的安装规则的所有外部项目,并且如果了解其他外部项目的位置,则理想情况下每个项目都应该能够找到自己的依赖项。如果这些事情中的任何一个不成立,那么顶级项目将不可避免地最终不得不对一个或多个项目的平台特定细节进行硬编码,此时超级构建的好处开始减少。
In general, the key thing to keep in mind is that superbuilds work well when all they do is bring together other external projects. They usually rely on all the external projects having well defined install rules and each of the projects should ideally be able to find their own dependencies if made aware of the location of the other external projects. If any of these things are not true, then the top level project will inevitably end up having to hard code platform specific details about one or more of the projects, at which point the benefits of a superbuild start decreasing.
如果项目没有依赖项,或者使用FetchContentgit 子模块之类的机制将依赖项引入到主构建中,那么一些前瞻性规划将有助于避免以后出现困难。真正有助于项目保持易于理解和使用的做法是将其顶层视为CMakeLists.txt更像目录。该结构可以分为以下几个部分:
If a project has no dependencies or if dependencies are being brought into the
main build using FetchContent or a mechanism like git submodules, then some
forward planning will help avoid difficulties later. A practice which really
helps a project to remain easy to understand and work with is to think of its
top level CMakeLists.txt as more like a table of contents. The structure can
be divided up into the following sections:
cmake_minimum_required()和 的调用project()。它还可能包括使用该
FetchContent模块来引入工具链文件和 CMake 帮助程序存储库等内容。这一部分通常应该很短。
cmake_minimum_required() and project(). It may also include some use of the
FetchContent module to bring in things like toolchain files and CMake helper
repositories. This section should typically be quite short.
CMakeLists.txt文件中定义它们相比,将它们放在专用目录中更干净并且具有鲁棒性优势。
CMakeLists.txt file, putting them in a dedicated directory is cleaner and has
robustness advantages.
add_subdirectory()调用。
add_subdirectory() calls.
上面重复出现的模式是,除了序言和项目范围的设置之外,大多数内容最好在通过添加的子目录中定义
add_subdirectory()。这不仅使顶级CMakeLists.txt
文件易于阅读和理解,还允许每个子目录专注于特定区域。这有助于使事情更容易找到,并且还意味着可以使用目录范围来最大限度地减少将不相关区域的变量暴露给不需要了解它们的事物。遵循上述准则的简单顶层示例CMakeLists.txt可能如下所示:
The recurring pattern in the above is that apart from the preamble and project
wide setup, most things are best defined in subdirectories added via
add_subdirectory(). Not only does this make the top level CMakeLists.txt
file easy to read and understand, it allows each subdirectory to focus on a
particular area. This helps make things easier to find and it also means
directory scopes can be used to minimize exposing variables from unrelated
areas to things that don’t need to know about them. An example of a simple top
level CMakeLists.txt that follows the above guidelines might look like this:
# Preamble
cmake_minimum_required(VERSION 3.1)
project(MyProj)
enable_testing()
# Project wide setup
list(APPEND CMAKE_MODULE_PATH
${CMAKE_CURRENT_LIST_DIR}/cmake
)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
# Externally provided content
add_subdirectory(dependencies)
# Main targets built by this project
add_subdirectory(src)
# Typically needed only if we are the top level project
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
add_subdirectory(tests)
add_subdirectory(packaging)
endif()# Preamble
cmake_minimum_required(VERSION 3.1)
project(MyProj)
enable_testing()
# Project wide setup
list(APPEND CMAKE_MODULE_PATH
${CMAKE_CURRENT_LIST_DIR}/cmake
)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
# Externally provided content
add_subdirectory(dependencies)
# Main targets built by this project
add_subdirectory(src)
# Typically needed only if we are the top level project
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
add_subdirectory(tests)
add_subdirectory(packaging)
endif()
实际上,项目范围的设置可能会包含比上面显示的更多的内容,并且可能还有其他目录用于项目构建的内容(例如用于文档、添加其他可安装内容,如脚本、图像等)。
In practice, the project wide setup will likely contain more than shown above and there may be other directories for things built by the project (e.g. for documentation, adding other installable content such as scripts, images and so on).
如果遵循上述关于在子目录中定义大多数内容的建议,项目源代码树的顶级目录通常将主要包含管理文件。其中可能包括某种自述文件、许可证详细信息、贡献说明等。持续集成系统还经常在顶级目录中查找特定的文件名。将源文件保留在该顶级目录之外可确保它始终专注于项目的高级描述。
If following the above advice about defining most things in subdirectories, the top directory of the project’s source tree will typically contain mostly just administrative files. These might include a readme file of some kind, license details, contribution instructions and so on. Continuous integration systems also frequently look for a particular file name in the top level directory. Keeping source files out of this top level directory ensures that it remains focused on the high level description of the project.
将依赖项处理委托给它自己的子目录可以实现一些重要的事情。首先,它确保没有依赖项可以看到
CMAKE_SOURCE_DIR并且CMAKE_CURRENT_SOURCE_DIR是相等的,因此他们可以依靠比较这两个变量来检测它们是否被合并到更大的项目结构中或者它们是独立构建的。上面的简单示例展示了当项目不是顶级项目时如何经常使用它来避免定义测试和打包细节。将所有依赖项处理放置在其自己的子目录中还可以确保用于设置依赖项的非缓存变量不会意外泄漏到构建的其他部分。这样做的一个有益的结果是,它还倾向于鼓励使用 CMake 目标而不是变量作为项目其余部分使用依赖项的方式。
Delegating the dependency handling to its own subdirectory achieves a couple of
important things. Firstly, it ensures that no dependency can ever see
CMAKE_SOURCE_DIR and CMAKE_CURRENT_SOURCE_DIR as being equal, so they can
rely on comparing these two variables to detect if they are being incorporated
into a larger project structure or they are being built standalone. The simple
example above shows how this is frequently used to avoid defining tests and
packaging details when the project is not the top level project. Placing all
dependency handling in its own subdirectory also ensures that no non-cache
variables used to set up the dependencies can bleed out to other parts of the
build accidentally. A beneficial consequence of this is that it also tends to
encourage the use of CMake targets rather than variables as the means by which
the rest of the project makes use of dependencies.
使用模块将项目的依赖项合并到构建中的示例FetchContent可能如下所示:
An example using the FetchContent module to incorporate the project’s
dependencies into the build might look like this:
include(FetchContent)
# Declare all the dependency details first in case any
# dependency wants to pull in some of the same ones
# (this keeps us in control)
FetchContent_Declare(jerry ...)
FetchContent_Declare(foo ...)
FetchContent_Declare(bar ...)
# Add the foo and bar dependencies if not already part of
# the build (two methods are shown)
# OPTION A (CMake 3.14 or later)
FetchContent_MakeAvailable(foo bar)
# OPTION B (CMake 3.13 or earlier)
FetchContent_GetProperties(foo)
if(NOT foo_POPULATED)
FetchContent_Populate(foo)
add_subdirectory(${foo_SOURCE_DIR} ${foo_BINARY_DIR})
endif()
FetchContent_GetProperties(bar)
if(NOT bar_POPULATED)
FetchContent_Populate(bar)
add_subdirectory(${bar_SOURCE_DIR} ${bar_BINARY_DIR})
endif()include(FetchContent)
# Declare all the dependency details first in case any
# dependency wants to pull in some of the same ones
# (this keeps us in control)
FetchContent_Declare(jerry ...)
FetchContent_Declare(foo ...)
FetchContent_Declare(bar ...)
# Add the foo and bar dependencies if not already part of
# the build (two methods are shown)
# OPTION A (CMake 3.14 or later)
FetchContent_MakeAvailable(foo bar)
# OPTION B (CMake 3.13 or earlier)
FetchContent_GetProperties(foo)
if(NOT foo_POPULATED)
FetchContent_Populate(foo)
add_subdirectory(${foo_SOURCE_DIR} ${foo_BINARY_DIR})
endif()
FetchContent_GetProperties(bar)
if(NOT bar_POPULATED)
FetchContent_Populate(bar)
add_subdirectory(${bar_SOURCE_DIR} ${bar_BINARY_DIR})
endif()
有时,依赖项可能需要在调用之前设置某些变量
add_subdirectory()。理想情况下,这将在其自己的范围内完成,以便它不会影响稍后在同一范围内添加的其他依赖项。因此,将每个依赖项群体也放入其自己的子目录中也是有用的,这将使上面的示例看起来像这样:
Sometimes, a dependency may require setting certain variables before calling
add_subdirectory(). Ideally, this would be done in its own scope so that it
can’t affect other dependencies added later in the same scope. It can therefore
be useful to put each dependency population in its own subdirectory too, which
would leave the above example looking like this:
include(FetchContent)
FetchContent_Declare(jerry ...)
FetchContent_Declare(foo ...)
FetchContent_Declare(bar ...)
add_subdirectory(foo)
add_subdirectory(bar)include(FetchContent)
FetchContent_Declare(jerry ...)
FetchContent_Declare(foo ...)
FetchContent_Declare(bar ...)
add_subdirectory(foo)
add_subdirectory(bar)
对于 3.13 或更早版本的最低 CMake 版本,子目录将如下所示(子目录bar可以执行类似的操作):
For a minimum CMake version of 3.13 or earlier, the subdirectories would then
look something like the following (the subdirectory for bar could do
something similar):
FetchContent_GetProperties(foo)
if(NOT foo_POPULATED)
FetchContent_Populate(foo)
# Add any customizations needed before actually pulling
# in the dependency. For example, build static libs by
# default and only build those targets that another
# target depends on.
set(BUILD_SHARED_LIBS NO)
set_directory_properties(PROPERTIES
EXCLUDE_FROM_ALL YES
)
# Now add the dependency
add_subdirectory(${foo_SOURCE_DIR} ${foo_BINARY_DIR})
endif()FetchContent_GetProperties(foo)
if(NOT foo_POPULATED)
FetchContent_Populate(foo)
# Add any customizations needed before actually pulling
# in the dependency. For example, build static libs by
# default and only build those targets that another
# target depends on.
set(BUILD_SHARED_LIBS NO)
set_directory_properties(PROPERTIES
EXCLUDE_FROM_ALL YES
)
# Now add the dependency
add_subdirectory(${foo_SOURCE_DIR} ${foo_BINARY_DIR})
endif()
使用 CMake 3.14 或更高版本,因为上面的内容在其自己的目录范围内,所以可以更简洁地表示为:
With CMake 3.14 or later, because the above is in its own directory scope, it could be expressed much more succinctly as:
set(BUILD_SHARED_LIBS NO)
set_directory_properties(PROPERTIES
EXCLUDE_FROM_ALL YES
)
FetchContent_MakeAvailable(foo)set(BUILD_SHARED_LIBS NO)
set_directory_properties(PROPERTIES
EXCLUDE_FROM_ALL YES
)
FetchContent_MakeAvailable(foo)
项目可以灵活地用于FetchContent_MakeAvailable()常见情况或显式调用相关命令以在需要时扩展逻辑。扩展规范模式的一个示例是处理预构建的二进制包或源包:
Projects have the flexibility to use FetchContent_MakeAvailable() for the
common cases or to invoke the relevant commands explicitly to extend the logic
where needed.
One example of extending the canonical pattern is to handle either a pre-built
binary package or a source package:
FetchContent_GetProperties(foo)
if(NOT foo_POPULATED)
FetchContent_Populate(foo)
if(EXISTS ${foo_SOURCE_DIR}/CMakeLists.txt)
# Probably source, but could still be a binary
# package that provides itself through a top level
# CMakeLists.txt file
add_subdirectory(
${foo_SOURCE_DIR} ${foo_BINARY_DIR}
)
else()
# Must be a binary package, assume it provides a
# config file in a standard location within its
# directory layout
find_package(foo REQUIRED
NO_DEFAULT_PATH
PATHS ${foo_SOURCE_DIR}
)
# For this to be useful, imported targets must be
# promoted to global so that other parts of the
# project can access them
set_target_properties(foo::foo PROPERTIES
IMPORTED_GLOBAL TRUE
)
endif()
endif()FetchContent_GetProperties(foo)
if(NOT foo_POPULATED)
FetchContent_Populate(foo)
if(EXISTS ${foo_SOURCE_DIR}/CMakeLists.txt)
# Probably source, but could still be a binary
# package that provides itself through a top level
# CMakeLists.txt file
add_subdirectory(
${foo_SOURCE_DIR} ${foo_BINARY_DIR}
)
else()
# Must be a binary package, assume it provides a
# config file in a standard location within its
# directory layout
find_package(foo REQUIRED
NO_DEFAULT_PATH
PATHS ${foo_SOURCE_DIR}
)
# For this to be useful, imported targets must be
# promoted to global so that other parts of the
# project can access them
set_target_properties(foo::foo PROPERTIES
IMPORTED_GLOBAL TRUE
)
endif()
endif()
如果规范模式足够,
FetchContent_MakeAvailable()则在最低 CMake 版本允许的情况下优先使用。该命令可能会在未来的 CMake 版本中获得更多功能,并且项目将免费获得这些功能,无需任何修改。与单独显式地写出每个依赖项的规范模式相比,它的编写和维护也要简单得多。
Where the canonical pattern is sufficient, prefer to use
FetchContent_MakeAvailable() if the minimum CMake version allows it.
The command may gain further capabilities in future CMake releases and
projects would then get those capabilities for free without any modification.
It is also vastly simpler to write and maintain than explicitly writing out the
canonical pattern for each dependency individually.
该FetchContent模块和IMPORTED_GLOBAL目标属性仅从 CMake 3.11 开始可用。在没有这些功能的情况下添加依赖项要困难得多,并且需要妥协一些推荐的原则或放弃添加预构建的二进制包的可能性。由于无法将本地目标提升为全局目标,替代方法通常依赖于通过变量将详细信息传递回主构建,或者必须定义全局目标以充当本地导入目标的代理。不太理想的方法直接从顶级CMakeLists.txt文件添加依赖项,但这使得项目很难合并到更大的项目层次结构中。如果不需要支持预构建的二进制包,则
IMPORTED_GLOBAL不需要目标属性,并且通常可以避免这些替代方法。为了支持 3.11 之前的 CMake 版本,git 子模块等技术file(DOWNLOAD)可能是
FetchContent模块的替代方案。
The FetchContent module and the IMPORTED_GLOBAL target property are only
available from CMake 3.11 onward. Adding dependencies without these features is
much harder and requires compromising on some of the recommended principles or
foregoing the possibility of adding pre-built binary packages. Without being
able to promote local targets to global, alternative methods generally rely on
passing details back to the main build via variables or global targets have to
be defined to act as proxies for local imported ones. A less desirable approach
adds dependencies directly from the top level CMakeLists.txt file, but that
makes the project hard to incorporate into a larger project hierarchy. If
pre-built binary packages do not need to be supported, then the
IMPORTED_GLOBAL target property isn’t needed and these alternative methods
can usually be avoided. For supporting CMake versions before 3.11, techniques
like git submodules or file(DOWNLOAD) might be alternatives to the
FetchContent module.
对于其他主项目的顶级子目录,添加tests不需要
packaging任何特殊内容,它们应该遵循前面章节中已经介绍的推荐实践。子目录的内容和结构tests将特定于项目,而packaging通常只需要一个CMakeLists.txt文件和可能的一些其他文件配置到构建目录中以供cpack. 它还可能包含某些包生成器使用的资源。目录的结构是一个更大的主题,在下面的第 29.5 节“定义目标”的src单独部分中进行了介绍。
Of the other main project’s top level subdirectories, adding tests and
packaging doesn’t require anything special, they should just follow the
recommended practices already covered in the preceding chapters. The contents
and structure of the tests subdirectory will be specific to the project,
while packaging typically only needs a CMakeLists.txt file and maybe a few
other files to be configured into the build directory for use by cpack. It
may also contain resources used by some of the package generators. The
structure of the src directory is a larger topic covered in its own section
in Section 29.5, “Defining Targets” further below.
上一节已经提到了一些目录名称,这些目录名称通常作为源树顶部正下方的子目录出现。扩展该列表以涵盖其他常用目录,结果如下:
The previous section already mentioned some directory names commonly found as subdirectories immediately below the top of the source tree. Expanding that list to cover other frequently used directories gives the following:
cmake
cmake
dependencies
dependencies
doc
doc
src
src
tests
tests
packaging
packaging
在没有任何其他约定的情况下,鼓励项目使用这些相同的目录名称。将 CMake 帮助程序脚本收集到cmake子目录中可以轻松查找它们,从而允许开发人员浏览该目录的内容并发现他们可能不知道的有用实用程序。只需list(APPEND CMAKE_MODULE_PATH …)在顶级CMakeLists.txt文件的项目范围设置部分中进行一次调用,即可将它们提供给整个项目。子目录doc是收集文档的便捷位置,如果使用 Markdown 或 Asciidoc 等格式且文件包含彼此的相对链接,则该子目录会很有用。
In the absence of any other conventions, projects are encouraged to use these
same directory names.
Collecting CMake helper scripts in a cmake subdirectory makes them easy to
find, allowing developers to browse through the contents of that directory and
discover useful utilities they may otherwise not have known about.
With just a single list(APPEND CMAKE_MODULE_PATH …) call in the
project-wide setup section of the top level CMakeLists.txt file, they can be
made available to the entire project.
The doc subdirectory can be a convenient place to collect documentation,
which can be useful if using formats like Markdown or Asciidoc and files
contain relative links to each other.
项目应该避免一些子目录名称。默认情况下,add_subdirectory()仅使用单个参数调用将在构建目录中产生同名的相应目录。项目应避免使用可能导致与构建区域中创建的预定义目录之一发生冲突的源目录名称。应避免使用的名称包括:
There are a few subdirectory names that projects should avoid. By default,
calling add_subdirectory() with just a single argument will result in a
corresponding directory of the same name in the build directory. The project
should avoid using a source directory name that may result in a clash with one
of the pre-defined directories created in the build area. Names to avoid
include the following:
Testing
Testing
CMakeFiles
CMakeFiles
CMakeScripts
CMakeScripts
CMAKE_CONFIGURATION_TYPES)。
CMAKE_CONFIGURATION_TYPES).
由于某些文件系统可能不区分大小写,因此上述所有名称不应以任何大小写组合使用。用作安装目标的其他常见目录名称也可能出现在构建目录中,具体取决于构建二进制位置所使用的策略(在下面第
29.5.2 节“目标输出”中进一步讨论)。bin因此,避免使用诸如、lib、share等源目录名称也是明智的man。
Since some file systems may be case insensitive, all of the above names should
not be used in any upper/lowercase combination. Other common directory names
used as install destinations may also appear in the build directory depending
on the strategy used for built binary locations (discussed further below in
Section 29.5.2, “Target Outputs”). Therefore, it would also be wise to avoid source directory
names like bin, lib, share, man and so on.
一些项目选择定义一个顶级include目录并在那里收集公共标头,而不是将它们放在关联的实现文件旁边。请注意,如果这样拆分,某些 IDE 工具可能无法自动查找标头,因此这样的安排对于某些开发人员来说可能不太方便。它还倾向于对特定功能或错误修复进行不太本地化的更改。另一方面,专用
include目录清楚地传达了哪些标头旨在公开,并且它们可以具有与安装时相同的目录结构。两种方法都有其优点,但是对于新开发人员来说,将标头与其关联的实现文件一起保存可能更简单一些。
A few projects choose to define a top level include directory and collect
public headers there rather than keeping them next to their associated
implementation files. Be aware that some IDE tools may be unable to find
headers automatically if they are split out like this, so such an arrangement
may be less convenient for some developers. It also tends to make changes for a
particular feature or bug fix less localized. On the other hand, a dedicated
include directory clearly communicates which headers are intended to be
public and they can have the same directory structure as they would when
installed. Both approaches have their merits, but keeping headers with their
associated implementation files is perhaps a little simpler for new developers.
使用 Xcode 或 Visual Studio 等项目生成器时,会在构建目录的顶部创建项目或解决方案文件。它可以在 IDE 中打开,就像该应用程序的任何其他项目文件一样,但它仍然处于 CMake 的控制之下。重要的是,这些项目文件是作为构建的一部分生成的,因此不应将它们签入版本控制系统。另请注意,下次运行 CMake 时,从 IDE 中对项目所做的更改将会丢失。
When using project generators such as Xcode or Visual Studio, a project or solution file is created at the top of the build directory. This can be opened in the IDE just like any other project file for that application, but it is still under the control of CMake. Importantly, these project files are generated as part of the build, so they should not be checked into a version control system. Also note that changes made to the project from within the IDE will be lost the next time CMake runs.
由于 Xcode 或 Visual Studio 项目文件是由 CMake 生成的,这意味着项目的目标和文件在项目层次结构或文件树中的呈现方式也在 CMake 项目的控制之下。CMake 提供了许多属性,这些属性可以影响目标和文件在某些 IDE 环境中的分组和标记方式。第一级分组是针对目标的,可以通过将USE_FOLDERS全局属性设置为 true 来启用。然后可以使用 target 属性指定每个目标的位置
FOLDER,该属性包含一个区分大小写的名称,该目标将放置在该名称下。要创建树状层次结构,可以使用正斜杠来分隔嵌套级别。如果该FOLDER属性为空或未设置,目标将在项目的顶层保持未分组状态。Xcode 和 Visual Studio 生成器都支持FOLDER目标属性。
Because the Xcode or Visual Studio project files are generated by CMake, this
means the way the project’s targets and files are presented in the project
hierarchy or file tree are also under the CMake project’s control. CMake
provides a number of properties that can influence how targets and files are
grouped and labeled in some IDE environments. The first level of grouping is
for targets, which can be enabled by setting the USE_FOLDERS global
property to true. The location of each target can then be specified using the
FOLDER target property, which holds a case sensitive name under which to
place that target. To create a tree-like hierarchy, forward slashes can be used
to separate the nesting levels. If the FOLDER property is empty or not set,
the target is left ungrouped at the top level of the project. Both the Xcode
and the Visual Studio generators honor the FOLDER target property.
set_property(GLOBAL PROPERTY USE_FOLDERS YES)
add_executable(Foo ...)
add_executable(Bar ...)
add_executable(test_Foo ...)
add_executable(test_Bar ...)
set_target_properties(Foo Bar PROPERTIES
FOLDER "Main apps"
)
set_target_properties(test_Foo test_Bar PROPERTIES
FOLDER "Main apps/Tests"
)set_property(GLOBAL PROPERTY USE_FOLDERS YES)
add_executable(Foo ...)
add_executable(Bar ...)
add_executable(test_Foo ...)
add_executable(test_Bar ...)
set_target_properties(Foo Bar PROPERTIES
FOLDER "Main apps"
)
set_target_properties(test_Foo test_Bar PROPERTIES
FOLDER "Main apps/Tests"
)
在 CMake 3.11 之前,FOLDERtarget 属性默认为空,而从 CMake 3.12 开始,它从变量的值初始化CMAKE_FOLDER
。
Up to CMake 3.11, the FOLDER target property is empty by default, whereas from
CMake 3.12, it is initialized from the value of the CMAKE_FOLDER
variable.
IDE 中为目标本身显示的名称默认与 CMake 使用的目标名称相同。Visual Studio 生成器允许通过设置目标属性来覆盖此显示名称PROJECT_LABEL,但 Xcode 生成器不支持此设置。
The name displayed for the target itself within the IDE defaults to the same
target name that CMake uses. Visual Studio generators allow this display name
to be overridden by setting the PROJECT_LABEL target property, but the
Xcode generator does not honor this setting.
set_target_properties(Foo PROPERTIES
PROJECT_LABEL "Foo Tastic"
)set_target_properties(Foo PROPERTIES
PROJECT_LABEL "Foo Tastic"
)
有些目标是由 CMake 本身创建的,例如用于安装、打包、运行测试等。对于 Xcode,其中大部分不会显示在文件/目标树中,但对于 Visual Studio,它们被分组在
CMakePredefinedTargets默认调用的文件夹下。这可以用
PREDEFINED_TARGETS_FOLDER全局属性覆盖,但通常没有理由这样做。
Some targets are created by CMake itself, such as for installing, packaging,
running tests and so on. For Xcode, most of these are not shown in the
file/target tree, but for Visual Studio they are grouped under a folder called
CMakePredefinedTargets by default. This can be overridden with the
PREDEFINED_TARGETS_FOLDER global property, but there is usually little
reason to do so.
每个目标下的各个文件的分组也可以由 CMake 项目控制。这是使用source_group()命令完成的,并且独立于目标文件夹分组(即始终支持,即使USE_FOLDERS全局属性为 false 或未设置)。该命令有两种形式,第一种用于定义单个组:
The grouping of individual files under each target can also be controlled by
the CMake project. This is done using the source_group() command and is
independent of the target folder grouping (i.e. it is always supported, even if
the USE_FOLDERS global property is false or unset). The command has two
forms, the first of which is used to define a single group:
source_group(group
[FILES src...]
[REGULAR_EXPRESSION regex]
)source_group(group
[FILES src...]
[REGULAR_EXPRESSION regex]
)
可以group是一个简单的名称,用于对相关文件进行分组,也可以指定类似于目标的层次结构。在 CMake 3.18 或更高版本中,正斜杠或反斜杠都可以用作嵌套级别之间的分隔符。对于 CMake 3.17 或更早版本,仅支持反斜杠。请注意,要正确完成 CMake 的解析,必须转义反斜杠,因此下面Foo嵌套有一个组Bar将被指定为如下所示:
The group can be a simple name under which to group the relevant files, or it
can specify a hierarchy similar to that for targets.
With CMake 3.18 or later, either forward slashes or back slashes can be used as
the separator between nesting levels.
For CMake 3.17 or earlier, only back slashes are supported.
Be aware that to get through CMake’s parsing correctly, back slashes must be
escaped, so a group Foo with a nested Bar underneath it would be specified
like so:
# Backslashes require escaping
source_group(Foo\\Bar ...)
# Forward slashes don't need escaping, but their
# support requires CMake 3.18 or later
source_group(Foo/Bar ...)# Backslashes require escaping
source_group(Foo\\Bar ...)
# Forward slashes don't need escaping, but their
# support requires CMake 3.18 or later
source_group(Foo/Bar ...)
可以使用参数指定单个文件FILES,并假定相对路径相对于CMAKE_CURRENT_SOURCE_DIR. 由于该命令不特定于目标,因此此选项可确保仅特定文件受到分组的影响。如果项目想要定义一个应该更广泛应用的分组结构,则该REGULAR_EXPRESSION
选项更合适。它可用于有效地设置将应用于项目中所有目标的分组规则。如果特定文件可以匹配多个分组,则FILES条目优先于
REGULAR_EXPRESSION. 当一个文件匹配多个正则表达式时,REGULAR_EXPRESSION后面定义的组优先于前面定义的组。
Individual files can be specified with the FILES argument, with relative
paths assumed to be relative to CMAKE_CURRENT_SOURCE_DIR. Because the command
is not specific to a target, this option is the way to ensure only specific
files are affected by the grouping. If the project wants to define a grouping
structure that should be applied more generally, the REGULAR_EXPRESSION
option is more appropriate. It can be used to effectively set up grouping rules
that will be applied to all targets in the project. Where a particular file
could match more than one grouping, a FILES entry takes precedence over a
REGULAR_EXPRESSION.
Where a file matches multiple regular expressions, REGULAR_EXPRESSION groups
defined later take precedence over those defined earlier.
以下示例为所有目标设置一般规则,以便具有常用源文件和头文件扩展名的文件将分组在
Sources. 测试源和标头将覆盖该分组并放置在一个Tests组下,而特殊情况special.cxx将放置在下面其自己的专用子组中Sources。
The following example sets up general rules for all targets such that files
with commonly used source and header file extensions will be grouped under
Sources. Test sources and headers will override that grouping and be placed
under a Tests group instead, while the special case special.cxx will be put
in its own dedicated subgroup below Sources.
source_group(Sources
REGULAR_EXPRESSION "\\.(c(xx|pp)?|hh?)$"
)
# Overrides the above
source_group(Tests
REGULAR_EXPRESSION "test.*"
)
# Overrides both of the above
source_group(Sources\\Special
FILES special.cxx
)source_group(Sources
REGULAR_EXPRESSION "\\.(c(xx|pp)?|hh?)$"
)
# Overrides the above
source_group(Tests
REGULAR_EXPRESSION "test.*"
)
# Overrides both of the above
source_group(Sources\\Special
FILES special.cxx
)
CMakeSource Files为源和Header Files标头提供了默认组,但这些很容易被覆盖,如上面的示例所示。还定义了其他默认组,例如Resources和。Object Files
CMake provides default groups Source Files for sources and Header Files for
headers, but these are easily overridden, as the above example demonstrates.
Other default groups such as Resources and Object Files are also defined.
该source_group()命令的第二种形式允许组层次结构遵循特定文件的目录结构。它可用于 CMake 3.8 或更高版本。
The second form of the source_group() command allows the group hierarchy to
follow the directory structure for specific files. It is available with CMake
3.8 or later.
source_group(TREE root
[PREFIX prefix]
[FILES src...]
)source_group(TREE root
[PREFIX prefix]
[FILES src...]
)
该TREE选项指示命令根据下面的目录结构对指定文件进行分组root。该PREFIX选项可用于将该分组结构放置在prefix父组或组层次结构下。这可以非常有效地与目标属性结合使用,SOURCES
以重现构成目标的所有源的目录结构,但前提是所有这些源都低于公共点(例如,没有从构建目录生成源)。许多目标都满足这些条件,因此通常可以使用以下示例模式来快速轻松地为目标在 IDE 中的呈现方式提供某种结构。
The TREE option directs the command to group the specified files according to
their own directory structure below root. The PREFIX option can be used to
place that grouping structure under the prefix parent group or group
hierarchy. This can be used very effectively in conjunction with the SOURCES
target property to reproduce the directory structure of all sources that make
up a target, but only if all of those sources are below a common point (e.g.
no generated sources from the build directory). Many targets satisfy these
conditions, so the following example pattern can often be used to quickly and
easily give some structure to the way a target is presented in an IDE.
# Only suitable if SOURCES does not contain generated
# files in this example
get_target_property(sources someTarget SOURCES)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}
PREFIX "Magic\\Sources"
FILES ${sources}
)# Only suitable if SOURCES does not contain generated
# files in this example
get_target_property(sources someTarget SOURCES)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}
PREFIX "Magic\\Sources"
FILES ${sources}
)
IDE 通常仅显示显式添加为目标源的文件。如果定义目标时仅将其实现文件添加为源,则其标头通常不会出现在 IDE 文件列表中。因此,通常的做法是显式列出标头,即使它们实际上不会被编译。除了将它们添加到 IDE 源列表之外,CMake 将有效地忽略它们。这不仅扩展到头文件,还可以用于添加其他非编译文件,例如图像、脚本和其他资源。某些功能(例如与
MACOSX_PACKAGE_LOCATION源属性关联的功能)需要将文件列为源文件才能生效。
IDEs generally only show files that are explicitly added as sources of a
target. If a target is defined with only its implementation files added as
sources, its headers won’t usually appear in the IDE file lists. Therefore, it
is common practice to explicitly list headers as well, even though they won’t
actually be compiled. CMake will effectively ignore them except to add
them to IDE source lists. This extends to more than just header files, it
can also be used to add other non-compiled files as well, such as images,
scripts and other resources. Some features such as those associated with the
MACOSX_PACKAGE_LOCATION source property require a file to be listed as a
source file to have any effect.
在某些情况下,可能需要源文件出现在 IDE 文件列表中但不被编译。只应在其他目标平台上编译和链接的特定于平台的文件就是一个例子。为了防止 CMake 尝试编译特定文件,
HEADER_FILE_ONLY可以将该源文件的 source 属性设置为 true(不要被属性名称混淆,它不仅可以用于标头)。
In certain situations, it may be desirable for a source file to appear in IDE
file lists but not be compiled. Platform-specific files that should only be
compiled and linked on other target platforms are an example of this. To
prevent CMake from trying to compile a particular file, that source file’s
HEADER_FILE_ONLY source property can be set to true (do not be confused
by the property name, it can be used for more than just headers).
add_executable(MyApp main.cpp net.cpp net_win.cpp)
if(NOT WIN32)
# Don't compile this file for non-Windows platforms
set_source_files_properties(net_win.cpp PROPERTIES
HEADER_FILE_ONLY YES
)
endif()add_executable(MyApp main.cpp net.cpp net_win.cpp)
if(NOT WIN32)
# Don't compile this file for non-Windows platforms
set_source_files_properties(net_win.cpp PROPERTIES
HEADER_FILE_ONLY YES
)
endif()
前面的章节介绍了一系列允许详细定义目标的 CMake 功能。这包括构建目标的源文件和其他文件、应如何构建目标以及目标如何与其他目标交互。本节的重点是演示如何使用这些技术,使项目易于理解、生成健壮的构建、提供灵活性并提高可维护性。
The preceding chapters have presented a range of CMake features that allow a target to be defined in detail. This includes the sources and other files that a target is built up from, how a target should be built and how a target interacts with other targets. The focus of this section is to demonstrate how to use these techniques in a way that makes the project easy to understand, produces a robust build, provides flexibility and promotes maintainability.
对于简单的项目,源文件和目标的数量可能很小,在这种情况下,在单个CMakeLists.txt文件中给出所有相关详细信息相对容易管理。如果遵循本章前面推荐的项目目录结构,这意味着
src子目录将不再有子目录,并且其
CMakeLists.txt文件将定义所需的所有内容。最初,它可能看起来像这样简单:
For simple projects, the number of source files and targets is likely to be
small, in which case it is relatively manageable for all of the relevant
details to be given in a single CMakeLists.txt file. If following the project
directory structure recommended earlier in this chapter, this would mean the
src subdirectory would have no further subdirectories and its
CMakeLists.txt file would define all that was needed. Initially, it may look
as simple as something like this:
add_executable(Planter main.cpp soy.cpp coffee.cpp)
target_compile_definitions(Planter
PUBLIC COFFEE_FAMILY=Robusta
)
add_test(NAME NoArgs COMMAND Planter)
add_test(NAME WithArgs COMMAND Planter beanType=soy)add_executable(Planter main.cpp soy.cpp coffee.cpp)
target_compile_definitions(Planter
PUBLIC COFFEE_FAMILY=Robusta
)
add_test(NAME NoArgs COMMAND Planter)
add_test(NAME WithArgs COMMAND Planter beanType=soy)
这对项目的使用方式做出了许多假设,但最大的假设可能是该项目不会被安装或打包,并且不会被其他项目吸收到更大的项目层次结构中。这些是可以而且应该避免的限制。上述简单案例的具体弱点包括:
This makes a number of assumptions about how the project will be used, but perhaps the biggest ones are that the project won’t be installed or packaged and that it won’t be absorbed into a larger project hierarchy by some other project. These are limitations that can and should be avoided. The specific weaknesses of the simple case above include:
install()后来添加了命令并实现了打包,其他项目也必须使用不同的目标名称来进行预构建的二进制文件与源包含。
install() command was
later added and packaging was implemented, other projects would have to use
different target names for pre-built binary versus source inclusion.
解决上述几点并遵循前面章节的推荐实践,该示例扩展为如下所示(假设项目名称为BagOfBeans):
Addressing the above points and following the recommended practices of the
previous chapters, the example expands out to more like the following
(assuming a project name of BagOfBeans):
#=============================
# Define targets
#=============================
add_executable(BagOfBeans_Planter
main.cpp soy.cpp soy.h coffee.cpp coffee.h
)
add_executable(BagOfBeans::Planter
ALIAS BagOfBeans_Planter
)
set_target_properties(BagOfBeans_Planter PROPERTIES
OUTPUT_NAME Planter
EXPORT_NAME Planter
)
target_compile_definitions(BagOfBeans_Planter
PUBLIC COFFEE_FAMILY=Robusta
)
#=============================
# Testing
#=============================
if(TEST_BAGOFBEANS OR
CMAKE_SOURCE_DIR STREQUAL BagOfBeans_SOURCE_DIR)
add_test(NAME Planter.NoArgs
COMMAND BagOfBeans_Planter
)
add_test(NAME Planter.WithArgs
COMMAND BagOfBeans_Planter beanType=soy
)
endif()
#=============================
# Packaging
#=============================
include(GNUInstallDirs)
install(TARGETS BagOfBeans_Planter
EXPORT BagOfBeans_Apps
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT BagOfBeans_Apps
)#=============================
# Define targets
#=============================
add_executable(BagOfBeans_Planter
main.cpp soy.cpp soy.h coffee.cpp coffee.h
)
add_executable(BagOfBeans::Planter
ALIAS BagOfBeans_Planter
)
set_target_properties(BagOfBeans_Planter PROPERTIES
OUTPUT_NAME Planter
EXPORT_NAME Planter
)
target_compile_definitions(BagOfBeans_Planter
PUBLIC COFFEE_FAMILY=Robusta
)
#=============================
# Testing
#=============================
if(TEST_BAGOFBEANS OR
CMAKE_SOURCE_DIR STREQUAL BagOfBeans_SOURCE_DIR)
add_test(NAME Planter.NoArgs
COMMAND BagOfBeans_Planter
)
add_test(NAME Planter.WithArgs
COMMAND BagOfBeans_Planter beanType=soy
)
endif()
#=============================
# Packaging
#=============================
include(GNUInstallDirs)
install(TARGETS BagOfBeans_Planter
EXPORT BagOfBeans_Apps
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT BagOfBeans_Apps
)
对于一个相当简单的可执行文件来说,这可能是一个令人惊讶的细节,但它强调了对于现实世界的项目,需要考虑的不仅仅是单独构建一个二进制文件。增加的复杂性主要是为了减少冲突的可能性的唯一名称。打包逻辑的添加还往往会添加大量细节,而缺乏经验的开发人员可能没有太多接触过这些细节。如上所示向文件添加清晰的部分可以帮助新开发人员更容易理解,并随着项目的发展保持文件井然有序。
That may be a surprising amount of detail for a fairly simple executable, but it highlights that for real world projects, there’s more to consider than just building a binary in isolation. The added complexity is mostly for unique names to reduce the likelihood of clashes. The addition of packaging logic also tends to add a fair amount of detail that an inexperienced developer probably hasn’t had much exposure to. Adding clear sections to the file as shown above can help make it easier to understand for newer developers and also keep it organized as the project evolves.
当源文件数量增加时,将它们全部放在一个目录中可能会使它们更难以使用。通常通过将它们放置在按功能分组的子目录下来解决这个问题,这也带来了一些其他好处。它不仅有助于防止事情变得过于混乱,还可以根据 CMake 缓存选项或其他配置时间逻辑轻松打开和关闭某些功能。例如:
When the number of source files increases, having them all in the one directory can make them more difficult to work with. This is generally addressed by placing them under subdirectories grouped by functionality, which brings a few other benefits too. Not only does it help keep things from becoming too cluttered, it also makes it easy to turn certain features on and off based on CMake cache options or other configure time logic. For example:
add_executable(BagOfBeans_Planter main.cpp)
option(BAGOFBEANS_SOY "Enable planting soy beans" ON)
option(BAGOFBEANS_COFFEE "Enable planting coffee beans" ON)
if(BAGOFBEANS_SOY)
add_subdirectory(soy)
endif()
if(BAGOFBEANS_COFFEE)
add_subdirectory(coffee)
endif()add_executable(BagOfBeans_Planter main.cpp)
option(BAGOFBEANS_SOY "Enable planting soy beans" ON)
option(BAGOFBEANS_COFFEE "Enable planting coffee beans" ON)
if(BAGOFBEANS_SOY)
add_subdirectory(soy)
endif()
if(BAGOFBEANS_COFFEE)
add_subdirectory(coffee)
endif()
在前面的所有章节中,可执行文件和库始终定义在一个目录中,因此可以直接向add_executable()或add_library()调用提供完整的文件列表。在上述安排中,子目录在使用命令定义目标后将源添加到目标
target_sources(),该命令在 CMake 3.1 或更高版本中可用。该命令的工作方式与其他target_…()命令一样,并且具有非常相似的形式:
In all of the preceding chapters, executables and libraries were always defined
in the one directory, so the full list of files could be supplied directly to
the add_executable() or add_library() call. In the above arrangement, the
subdirectories add sources to the target after it has been defined using the
target_sources() command, which is available with CMake 3.1 or later.
This command works just like the other target_…() commands and has a very
similar form:
target_sources(targetName
<PRIVATE|PUBLIC|INTERFACE> src...
# Repeat with more sections as needed
...
)target_sources(targetName
<PRIVATE|PUBLIC|INTERFACE> src...
# Repeat with more sections as needed
...
)
提供了一个或多个或PRIVATE部分,每个部分都列出了要添加到相关目标的源文件。
源被添加到的属性,而
源被添加到属性。源
已添加到这两个属性中。更实际的思考方式是将源代码编译为,将源代码添加到链接到的任何内容中,并将源代码添加到两者中。PUBLICINTERFACEPRIVATESOURCEStargetNameINTERFACEINTERFACE_SOURCESPUBLICPRIVATEtargetNameINTERFACEtargetNamePUBLIC
One or more PRIVATE, PUBLIC or INTERFACE sections is provided, each of
which lists source files to be added to the relevant target.
PRIVATE sources are added to the SOURCES property of targetName, while
INTERFACE sources are added to the INTERFACE_SOURCES property. A PUBLIC
source is added to both properties. The more practical way of thinking of this
is that PRIVATE sources are compiled into targetName, INTERFACE sources
are added to anything that links to targetName and PUBLIC sources are added
to both.
在实践中,除此之外的任何情况PRIVATE都是不寻常的,因为将源文件添加到所有链接目标的targetName用途将受到限制。人们可以使用它来添加需要成为同一翻译单元一部分才能工作的资源,或者嵌入不应通过任何库间接口公开的内容,但这些情况并不常见。
In practice, anything other than PRIVATE would be unusual, since adding a
source file to all targets linking against targetName would have limited
usefulness. One could use it to add resources that need to be part of the same
translation unit to work, or to embed something that should not be exposed
through any inter-library interface, but these situations would be uncommon.
CMake 3.13 之前的命令的一个特点target_sources()是,如果使用相对路径指定源,则假定该路径相对于它要添加到的目标的源目录。这会产生许多问题。第一个是,如果将其添加为INTERFACE
源,则路径将被视为相对于其他目标,而不是
targetName。显然,这可能会创建不正确的路径,因此任何非PRIVATE
源都需要使用绝对路径指定。第二个问题是,当从定义target_sources()目录以外的目录调用相对路径时,相对路径的行为会不直观。targetName考虑如何CMakeLists.txt指定前面示例的目录之一的文件:
A peculiarity of the target_sources() command prior to CMake 3.13 is that if
a source is specified with a relative path, that path is assumed to be relative
to the source directory of the target it is being added to. This creates a
number of problems. The first is that if it were added as an INTERFACE
source, then the path would be treated as relative to that other target, not
targetName. Clearly this could create incorrect paths, so any non-PRIVATE
source would need to be specified with an absolute path. The second problem is
that relative paths behave unintuitively when target_sources() is called from
a directory other than the one in which targetName was defined. Consider how
the CMakeLists.txt file for one of the directories of the earlier example
might be specified:
# WARNING: Wrong file paths with CMake 3.12 or earlier
target_sources(BagOfBeans_Planter
PRIVATE
coffee.cpp
coffee.h
)
...# WARNING: Wrong file paths with CMake 3.12 or earlier
target_sources(BagOfBeans_Planter
PRIVATE
coffee.cpp
coffee.h
)
...
上面的目的是从同一目录添加源,但使用 CMake 3.12 或更早版本,它们将被解释为相对于src
而不是src/coffee. 解决此问题的最可靠方法是为它们添加前缀
CMAKE_CURRENT_SOURCE_DIR或CMAKE_CURRENT_LIST_DIR以确保它们始终使用正确的路径:
The above is intended to add the sources from the same directory, but with
CMake 3.12 or earlier, they will be interpreted as being relative to src
rather than src/coffee.
The most robust way to address this is to prefix them with
CMAKE_CURRENT_SOURCE_DIR or CMAKE_CURRENT_LIST_DIR to ensure they always
use the correct path:
target_sources(BagOfBeans_Planter
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/coffee.cpp
${CMAKE_CURRENT_LIST_DIR}/coffee.h
)
target_compile_definitions(BagOfBeans_Planter
PUBLIC COFFEE_FAMILY=Robusta
)
target_include_directories(BagOfBeans_Planter
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)target_sources(BagOfBeans_Planter
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/coffee.cpp
${CMAKE_CURRENT_LIST_DIR}/coffee.h
)
target_compile_definitions(BagOfBeans_Planter
PUBLIC COFFEE_FAMILY=Robusta
)
target_include_directories(BagOfBeans_Planter
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
在这样的用例中,必须为每个源文件添加前缀 或 ,${CMAKE_CURRENT_SOURCE_DIR}这
${CMAKE_CURRENT_LIST_DIR}很不方便,也不是特别直观。认识到这一点,CMake 3.13 中的行为发生了变化,将相对路径视为相对于
CMAKE_CURRENT_SOURCE_DIR调用点target_sources(),而不是定义目标的源目录。策略CMP0076为那些依赖旧行为的项目提供向后兼容性。
Having to prefix each source file with ${CMAKE_CURRENT_SOURCE_DIR} or
${CMAKE_CURRENT_LIST_DIR} in use cases such as this is inconvenient and not
particularly intuitive. In recognition of this, the behavior was changed in
CMake 3.13 to treat relative paths as being relative to
CMAKE_CURRENT_SOURCE_DIR at the point where target_sources() is called, not
the source directory in which the target was defined. Policy CMP0076 provides
backward compatibility for those projects that were relying on the old
behavior.
上面还演示了如何target_…()将其他命令也移动到子目录中,而不仅仅是target_sources(). 这有助于使内容保持在与其相关的代码本地。例如,仅当启用特定功能时,才能添加特定于该功能的编译定义、编译器标志和标头搜索路径。如果需要重新组织目录结构并且该目录移动到其他地方,则该文件中的任何内容都不需要更改,并且目标中的其他源将#include "coffee.h"继续工作而无需修改。
The above also demonstrates how other target_…() commands can be moved into
the subdirectories too, not just target_sources(). This helps keep things
local to the code they relate to. For example, compile definitions, compiler
flags and header search paths that are specific to a particular feature can be
added only if that feature is enabled. If the directory structure needed to be
reorganized and this directory moved elsewhere, nothing in this file would need
to change and other sources in the target that had #include "coffee.h" would
continue to work unmodified.
这种细节本地化的一个例外是target_link_libraries()。CMake 3.12 及更早版本禁止target_link_libraries()在不同目录中定义的目标上进行操作。如果子目录需要将目标链接到某些内容,则无法从该子目录中执行此操作。对 的调用target_link_libraries()必须在与调用add_executable()或 的目录相同的目录中进行add_library()。例如,如果
BagOfBeans_Planter目标需要链接到名为 的库Weather,则必须添加调用src/CMakeLists.txt而不是
src/coffee/CMakeLists.txt。这将导致如下所示的结果:
The one exception to this localization of details is target_link_libraries().
CMake 3.12 and earlier prohibited target_link_libraries() from operating on a
target defined in a different directory. If a subdirectory needed to make the
target link to something, it couldn’t do it from within that subdirectory. The
call to target_link_libraries() would have to be made in the same directory
as where add_executable() or add_library() was called. If, for example, the
BagOfBeans_Planter target needed to link against a library called Weather,
it would have to add the call in src/CMakeLists.txt rather than
src/coffee/CMakeLists.txt. This would result in something like the following:
option(BAGOFBEANS_COFFEE "Enable planting coffee beans" ON)
if(BAGOFBEANS_COFFEE)
add_subdirectory(coffee)
target_link_libraries(BagOfBeans_Planter
PRIVATE Weather
)
endif()option(BAGOFBEANS_COFFEE "Enable planting coffee beans" ON)
if(BAGOFBEANS_COFFEE)
add_subdirectory(coffee)
target_link_libraries(BagOfBeans_Planter
PRIVATE Weather
)
endif()
CMake 3.13 取消了这一限制,允许子目录真正独立。对于从 3.1 到 3.12 的 CMake 版本,除了添加目标应链接到的库之外,子目录可以完全独立。在 CMake 3.1 之前,需要一种完全不同的方法,该方法依赖于在变量中构建源列表,并且仅在添加所有子目录后才创建目标。这样的安排可能如下所示:
CMake 3.13 lifted this restriction, allowing subdirectories to be truly self-contained. For CMake versions from 3.1 to 3.12, subdirectories can be fully self-contained apart from adding libraries that a target should link to. Before CMake 3.1, a completely different approach was needed which relied on building up lists of sources in a variable and only creating the target once all subdirectories had been added. Such an arrangement might look like this:
# Pre-CMake 3.1 method, avoid using this approach
unset(planterSources)
unset(planterDefines)
unset(planterOptions)
unset(planterLinkLibs)
# Subdirs add to the above variables using PARENT_SCOPE
option(BAGOFBEANS_SOY "Enable planting soy beans" ON)
option(BAGOFBEANS_COFFEE "Enable planting coffee beans" ON)
if(BAGOFBEANS_SOY)
add_subdirectory(soy)
endif()
if(BAGOFBEANS_COFFEE)
add_subdirectory(coffee)
endif()
# Lastly define the target and its other details.
# All variables are assumed to name PRIVATE items.
add_executable(BagOfBeans_Planter ${planterSources})
target_compile_definitions(BagOfBeans_Planter
PRIVATE ${planterDefines}
)
target_compile_options(BagOfBeans_Planter
PRIVATE ${planterOptions}
)
target_link_libraries(BagOfBeans_Planter
PRIVATE ${planterLinkLibs}
)# Pre-CMake 3.1 method, avoid using this approach
unset(planterSources)
unset(planterDefines)
unset(planterOptions)
unset(planterLinkLibs)
# Subdirs add to the above variables using PARENT_SCOPE
option(BAGOFBEANS_SOY "Enable planting soy beans" ON)
option(BAGOFBEANS_COFFEE "Enable planting coffee beans" ON)
if(BAGOFBEANS_SOY)
add_subdirectory(soy)
endif()
if(BAGOFBEANS_COFFEE)
add_subdirectory(coffee)
endif()
# Lastly define the target and its other details.
# All variables are assumed to name PRIVATE items.
add_executable(BagOfBeans_Planter ${planterSources})
target_compile_definitions(BagOfBeans_Planter
PRIVATE ${planterDefines}
)
target_compile_options(BagOfBeans_Planter
PRIVATE ${planterOptions}
)
target_link_libraries(BagOfBeans_Planter
PRIVATE ${planterLinkLibs}
)
如果某些项目需要是 以外的任何内容,则上述情况会变得更加复杂PRIVATE。像这样的变量的使用是脆弱的,因为它不依赖于子目录中的任何内容,为不同的目标使用相同的变量,并且变量名称中的拼写错误不会被 CMake 捕获为错误。它还强制父目录和子目录之间更强的耦合,因为每个子子目录都必须使用set(… PARENT_SCOPE). 对于深度嵌套的目录,这很快就会变得乏味并且容易出错。
The above would get even more complicated if some items needed to be anything
other than PRIVATE. The use of variables like this is fragile, as it relies
on nothing in subdirectories using the same variables for a different target
and typos in variable names would not be caught as an error by CMake. It also
enforces a stronger coupling between parent and child directories, since each
child subdirectory would have to pass all relevant variables back up to its
parent using set(… PARENT_SCOPE). For deeply nested directories, this
quickly gets tedious and is error-prone.
上面讨论了将源添加到库和可执行目标的方法。CMake 3.20 还添加了通过以下方式将源添加到自定义目标的功能
target_sources()。在 CMake 3.20 之前,只能将源添加到调用中的自定义目标
add_custom_target()。启用它可以通过target_sources()允许将源增量添加到自定义目标,例如从以某种方式扩展自定义目标的子目录中的调用。
The above discusses ways to add sources to library and executable targets.
CMake 3.20 added the ability to add sources to custom targets via
target_sources() as well.
Prior to CMake 3.20, sources could only be added to custom targets in the
add_custom_target() call.
Enabling it to be done via target_sources() allows sources to be added to a
custom target incrementally, such as from calls within subdirectories that
extend the custom target in some way.
当构建库或可执行文件时,其默认位置将是
CMAKE_CURRENT_BINARY_DIR或其下面的特定于配置的子目录,具体取决于所使用的生成器。对于具有许多子目录或深层嵌套层次结构的项目,这可能会给开发人员带来不便。可以使用以下目标属性集覆盖此默认值:
When a library or executable is built, its default location will be either
CMAKE_CURRENT_BINARY_DIR or a configuration-specific subdirectory below it,
depending on the generator used.
For projects with many subdirectories or deeply nested hierarchies, this can
be inconvenient for developers.
This default can be overridden using the following set of target properties:
RUNTIME_OUTPUT_DIRECTORY
RUNTIME_OUTPUT_DIRECTORY
LIBRARY_OUTPUT_DIRECTORY
LIBRARY_OUTPUT_DIRECTORY
ARCHIVE_OUTPUT_DIRECTORY
ARCHIVE_OUTPUT_DIRECTORY
对于上述所有三个,多配置生成器(如 Visual Studio、Xcode 和 Ninja Multi-Config)将自动将特定于配置的子目录附加到每个值,除非它包含生成器表达式。由于历史原因,还支持将每个配置属性与_<CONFIG>附加属性相关联,但应避免这些属性,以便在需要配置特定行为的情况下使用生成器表达式。
For all three of the above, multi configuration generators like Visual Studio,
Xcode and Ninja Multi-Config will automatically append a configuration-specific
subdirectory to each value unless it contains a generator expression.
Associated per configuration properties with _<CONFIG> appended are also
supported for historical reasons, but those should be avoided in favor of using
generator expressions where configuration specific behavior is needed.
这些目标属性的常见用途是将库和可执行文件收集到与安装时类似的目录结构中。如果应用程序希望各种资源位于相对于可执行文件的二进制文件的特定位置,这将很有帮助。在 Windows 上,它还可以简化调试,因为可执行文件和 DLL 可以收集到同一目录中,从而允许可执行文件自动查找其 DLL 依赖项(在其他平台上不需要这样做,因为支持将必要的RPATH位置嵌入到二进制文件本身中) )。
A common use of these target properties is to collect libraries and executables
together in a similar directory structure as they would have when installed.
This is helpful if applications expect various resources to be located at a
particular location relative to the executable’s binary. On Windows, it can
also simplify debugging, since executables and DLLs can be collected into the
same directory, allowing the executables to find their DLL dependencies
automatically (this isn’t needed on other platforms, since RPATH support
embeds the necessary locations in the binaries themselves).
按照通常的模式,这些目标属性均由具有相同名称且CMAKE_前置的 CMake 变量初始化。当所有目标应使用相同的一致输出位置时,可以在项目顶部设置这些变量,以便不必为每个目标单独设置属性。为了允许项目合并到更大的项目层次结构中,只有在尚未设置这些变量时才应设置这些变量,以便父项目可以覆盖输出位置。CMAKE_CURRENT_BINARY_DIR他们还应该使用相对于而不是的
位置CMAKE_BINARY_DIR。以下示例演示如何安全地收集stage当前二进制目录的子目录下的二进制文件,除非父项目覆盖它。
Following the usual pattern, these target properties are each initialized by a
CMake variable of the same name with CMAKE_ prepended. When all targets
should use the same consistent output locations, these variables can be set at
the top of the project so that the properties don’t have to be set for
every target individually. To allow the project to be incorporated into a
larger project hierarchy, these variables should only be set if they are not
already set so that parent projects can override the output locations. They
should also use a location relative to CMAKE_CURRENT_BINARY_DIR rather than
CMAKE_BINARY_DIR. The following example shows how to safely collect binaries
under a stage subdirectory of the current binary directory unless a parent
project overrides this.
set(stageDir ${CMAKE_CURRENT_BINARY_DIR}/stage)
include(GNUInstallDirs)
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
${stageDir}/${CMAKE_INSTALL_BINDIR}
)
endif()
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
${stageDir}/${CMAKE_INSTALL_LIBDIR}
)
endif()
if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
${stageDir}/${CMAKE_INSTALL_LIBDIR}
)
endif()set(stageDir ${CMAKE_CURRENT_BINARY_DIR}/stage)
include(GNUInstallDirs)
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
${stageDir}/${CMAKE_INSTALL_BINDIR}
)
endif()
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
${stageDir}/${CMAKE_INSTALL_LIBDIR}
)
endif()
if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
${stageDir}/${CMAKE_INSTALL_LIBDIR}
)
endif()
避免创建CMAKE_…_OUTPUT_DIRECTORY为缓存变量,它们不应该在开发人员的控制之下。它们应该由项目控制,因为项目的某些部分可能会对二进制文件的相对布局做出假设。更重要的是,将它们保留为普通变量还意味着可以在定义测试可执行文件的子目录中取消设置它们,从而避免它们与其他主要二进制文件一起收集并弄乱该区域。
Avoid creating CMAKE_…_OUTPUT_DIRECTORY as cache variables, they should
not be under the control of the developer. They should be controlled by the
project because parts of the project may make assumptions about the
relative layout of the binaries. More importantly, leaving them as ordinary
variables also means they can be unset within subdirectories where test
executables are defined, allowing them to avoid being collected with the other
main binaries and cluttering up that area.
构建的二进制文件的名称也可以由项目控制。默认情况下,二进制文件的基本名称将与目标名称相同。当目标名称遵循合并项目名称的约定(以帮助它们在较大项目层次结构的一部分时保持唯一)时,目标名称可能不适合作为二进制基本名称。targetOUTPUT_NAME属性可用于覆盖基本名称,或者为了进行更细粒度的控制,可以设置更具体的RUNTIME_OUTPUT_NAME,
LIBRARY_OUTPUT_NAME和属性。ARCHIVE_OUTPUT_NAME在大多数情况下,OUTPUT_NAME这既足够又是首选。所有这些属性都支持生成器表达式。
The name of the built binary can also be controlled by the project.
By default, the base name of the binary will be the same as the target name.
When target names follow the convention of incorporating the project name (to
help keep them unique when part of a larger project hierarchy), the target
name may not be appropriate as the binary base name.
The OUTPUT_NAME target property can be used to override the base name,
or for more fine-grained control, the more specific RUNTIME_OUTPUT_NAME,
LIBRARY_OUTPUT_NAME and ARCHIVE_OUTPUT_NAME properties can be set.
In most cases, OUTPUT_NAME is both sufficient and preferred.
All of these properties support generator expressions.
add_executable(BagOfBeans_Planter ...)
set_target_properties(BagOfBeans_Planter PROPERTIES
OUTPUT_NAME Planter
)add_executable(BagOfBeans_Planter ...)
set_target_properties(BagOfBeans_Planter PROPERTIES
OUTPUT_NAME Planter
)
较旧的项目有时会读取LOCATION目标属性以尝试获取二进制文件的输出位置并在自定义目标命令等地方使用它。正如第 14.4 节“推荐实践”中已经强调的那样,这对于多配置生成器来说是有问题的,因为位置取决于配置,而LOCATION目标属性没有考虑到这一点。项目应该使用生成器表达式$<TARGET_FILE:…>来代替。如果项目尝试设置此目标属性,CMake 3.0 及更高版本将发出警告。
Older projects sometimes read the LOCATION target property to try to
obtain the output location of a binary and use it in places like custom target
commands.
As already highlighted in Section 14.4, “Recommended Practices”, this is problematic for
multi-configuration generators, since the location depends on the
configuration and the LOCATION target property doesn’t account for that.
Projects should use generator expressions like $<TARGET_FILE:…> instead.
CMake 3.0 and later will warn if a project tries to set this target property.
Windows 缺乏支持给RPATH开发人员带来了许多问题。在开发过程中运行可执行文件时,可执行文件所需的任何 DLL 必须位于同一目录中或位于环境PATH变量中列出的目录之一中。对于项目的主要二进制文件,…_OUTPUT_PATH可以使用各种属性将可执行文件和库放在同一位置,但这种技术对于测试可执行文件不太方便,因为它们可能有很多,并且将它们全部放在一个输出目录中可能会出现问题。
Windows’ lack of support for RPATH causes a number of problems for
developers. When running an executable during development, any DLLs the
executable requires must be either in the same directory or be located in one
of the directories listed in the PATH environment variable. For the project’s
main binaries, the various …_OUTPUT_PATH properties can be used to place
executables and libraries in the same location, but this technique is less
convenient for test executables since there could be many of them and having
them all in the one output directory can be problematic.
对于通过 执行的测试ctest,ENVIRONMENT可以使用 test 属性添加所需的 DLL 目录,如下所示PATH:
For tests executed through ctest, the ENVIRONMENT test property can be
used to add the required DLL directories to the PATH like so:
add_executable(test_Foo ...)
target_link_libraries(test_Foo PRIVATE Algo)
add_test(NAME FooWithAlgo COMMAND test_Foo)
if(WIN32)
set(algoDir "$<SHELL_PATH:$<TARGET_FILE_DIR:Algo>>")
set(execPath "PATH=${algoDir}$<SEMICOLON>$ENV{PATH}")
set_tests_properties(FooWithAlgo PROPERTIES
ENVIRONMENT "${execPath}"
)
endif()add_executable(test_Foo ...)
target_link_libraries(test_Foo PRIVATE Algo)
add_test(NAME FooWithAlgo COMMAND test_Foo)
if(WIN32)
set(algoDir "$<SHELL_PATH:$<TARGET_FILE_DIR:Algo>>")
set(execPath "PATH=${algoDir}$<SEMICOLON>$ENV{PATH}")
set_tests_properties(FooWithAlgo PROPERTIES
ENVIRONMENT "${execPath}"
)
endif()
这对于尝试在 Visual Studio IDE 中运行测试可执行文件没有帮助。CMake 3.12 引入了VS_DEBUGGER_COMMAND和
VS_DEBUGGER_WORKING_DIRECTORYtarget 属性。VS_DEBUGGER_COMMAND_ARGUMENTS然后,CMake 3.13 通过添加和 以及VS_DEBUGGER_ENVIRONMENT对所有四个属性的生成器表达式支持,
进一步扩展了目标属性集
。可用于在 Visual Studio IDE 中运行目标时VS_DEBUGGER_ENVIRONMENT设置环境变量。PATH上面的示例可以扩展为使用此属性,如下所示:
This won’t help with trying to run the test executable within the Visual Studio
IDE.
CMake 3.12 introduced the VS_DEBUGGER_COMMAND and
VS_DEBUGGER_WORKING_DIRECTORY target properties.
CMake 3.13 then expanded the set of target properties further by adding
VS_DEBUGGER_COMMAND_ARGUMENTS and VS_DEBUGGER_ENVIRONMENT, along
with generator expression support for all four properties.
VS_DEBUGGER_ENVIRONMENT can be used to set the PATH environment variable
when running the target within the Visual Studio IDE.
The above example can be extended to use this property as follows:
if(WIN32)
set(algoDir "$<SHELL_PATH:$<TARGET_FILE_DIR:Algo>>")
set(execPath "PATH=${algoDir}$<SEMICOLON>$ENV{PATH}")
set_tests_properties(FooWithAlgo PROPERTIES
ENVIRONMENT "${execPath}"
)
set_target_properties(test_Foo PROPERTIES
VS_DEBUGGER_ENVIRONMENT "${execPath}"
)
endif()if(WIN32)
set(algoDir "$<SHELL_PATH:$<TARGET_FILE_DIR:Algo>>")
set(execPath "PATH=${algoDir}$<SEMICOLON>$ENV{PATH}")
set_tests_properties(FooWithAlgo PROPERTIES
ENVIRONMENT "${execPath}"
)
set_target_properties(test_Foo PROPERTIES
VS_DEBUGGER_ENVIRONMENT "${execPath}"
)
endif()
VS_DEBUGGER_COMMAND如果需要, 和属性VS_DEBUGGER_COMMAND_ARGUMENTS可用于自定义要在 IDE 中为目标执行的命令。该VS_DEBUGGER_WORKING_DIRECTORY属性可用于覆盖执行命令的目录。VS_DEBUGGER_…Visual Studio 2010 或更高版本支持所有这些属性。
The VS_DEBUGGER_COMMAND and VS_DEBUGGER_COMMAND_ARGUMENTS properties can
be used to customize the command to be executed for the target within the IDE
if needed.
The VS_DEBUGGER_WORKING_DIRECTORY property can be used to override the
directory from which the command is executed.
All of these VS_DEBUGGER_… properties are supported for Visual Studio 2010
or later.
如果项目的最低 CMake 版本限制阻止
VS_DEBUGGER_…使用属性,则需要更详细的措施。CMake 3.8 添加了对目标属性的支持VS_USER_PROPS,该属性可用于基于每个目标覆盖用户属性文件的位置。可以创建自定义属性文件,并将其
条目设置为要与默认值合并的LocalDebuggerEnvironment其他条目。如果任何测试所需的 DLL 都集中在少量位置,则可以为每个测试生成并重复使用一个用户属性文件(但如果需要,仍然可以为每个目标生成并使用自定义用户属性文件) )。该命令可用于自动填充输出目录。用户属性文件不仅仅可以设置调试器环境,但以下基本示例为那些希望进一步探索此技术的人提供了一个起点:PATHPATHconfigure_file()
If the project’s minimum CMake version constraints prevent
VS_DEBUGGER_… properties from being used, more elaborate measures are
needed.
CMake 3.8 added support for the VS_USER_PROPS target property which can
be used to override the location of the user properties file on a per-target
basis. A custom properties file can be created with its
LocalDebuggerEnvironment entry set to the additional PATH entries to be
merged with the default PATH. If the DLLs any tests need are
collected together in a small number of locations, one user properties
file can be generated and re-used for each test (but it is still possible to
generate and use a custom user properties file for each target if required).
The configure_file() command can be used to fill in the output directory
automatically.
User property files can do more than just set up the debugger environment, but
the following basic example provides a starting point for those wishing to
explore this technique further:
file(TO_NATIVE_PATH
${CMAKE_RUNTIME_OUTPUT_DIRECTORY} baseDir
)
configure_file(user.props.in user.props @ONLY)file(TO_NATIVE_PATH
${CMAKE_RUNTIME_OUTPUT_DIRECTORY} baseDir
)
configure_file(user.props.in user.props @ONLY)
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LocalDebuggerEnvironment>PATH=@baseDir@\Debug</LocalDebuggerEnvironment>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LocalDebuggerEnvironment>PATH=@baseDir@\Release</LocalDebuggerEnvironment>
</PropertyGroup>
</Project><?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LocalDebuggerEnvironment>PATH=@baseDir@\Debug</LocalDebuggerEnvironment>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LocalDebuggerEnvironment>PATH=@baseDir@\Release</LocalDebuggerEnvironment>
</PropertyGroup>
</Project>
Windows 的另一个独特之处是,对于可执行文件和 DLL,通常会生成 PDB(程序数据库)文件,以便在开发过程中提供调试信息。PDB 文件有两种,CMake 为这两种文件提供了功能。对于共享库和可执行文件,可以使用PDB_NAME特定于配置的PDB_NAME_<CONFIG>目标属性来覆盖 PDB 文件的基本名称。默认名称通常是最合适的,因为它与 DLL 或可执行文件名称匹配,只是它有一个
.pdb后缀而不是.dll或.exe。默认情况下,PDB 文件放置在与 DLL 或可执行文件相同的目录中,但这可以使用PDB_OUTPUT_DIRECTORY和配置特定的
PDB_OUTPUT_DIRECTORY_<CONFIG>目标属性覆盖。请注意,与其他…_OUTPUT_DIRECTORY属性不同,PDB_OUTPUT_DIRECTORYCMake 3.11 或更早版本不支持生成器表达式。
Another aspect unique to Windows is that for executables and DLLs, it is
typical for a PDB (program database) file to be generated so that debugging
information is available during development.
There are two kinds of PDB files and CMake provides features for both.
For shared libraries and executables, the PDB_NAME and
configuration specific PDB_NAME_<CONFIG> target properties can be used to
override the base name of the PDB file. The default name is normally the most
appropriate though, since it matches the DLL or executable name except it has a
.pdb suffix instead of .dll or .exe. The PDB file is placed in the same
directory as the DLL or executable by default, but this can be overridden with
the PDB_OUTPUT_DIRECTORY and configuration specific
PDB_OUTPUT_DIRECTORY_<CONFIG> target properties. Note that unlike the
other …_OUTPUT_DIRECTORY properties, PDB_OUTPUT_DIRECTORY does not
support generator expressions with CMake 3.11 or earlier.
还创建第二种 PDB 文件,其中保存为目标构建的各个对象文件的信息。这个 PDB 文件在开发过程中不太有用,除了静态库。对于 C++,后一个 PDB 文件有一个默认名称,VCxx.pdb其中xx代表正在使用的 Visual C++ 版本(例如VC14.pdb)。由于默认名称不是特定于目标的,因此在某些情况下很容易出错并混淆不同目标的 PDB。COMPILE_PDBCMake 允许使用目标属性或关联的特定于配置的目标属性来控制每个目标的对象 PDB 文件的名称COMPILE_PDB_<CONFIG>。这些对象 PDB 文件的位置也可以使用
COMPILE_PDB_OUTPUT_DIRECTORY和
COMPILE_PDB_OUTPUT_DIRECTORY_<CONFIG>target 属性覆盖。请注意,这些对象 PDB 文件对于 DLL 和可执行目标几乎没有什么用处,因为主 PDB 已经包含所需的所有调试信息。
A second kind of PDB file is also created which holds information for the
individual object files being built for a target. This PDB file is less useful
during development, except perhaps for static libraries. For C++, this latter
PDB file has a default name VCxx.pdb where xx represents the version of
Visual C++ being used (e.g. VC14.pdb). Because the default name is not
target-specific, it is easy to make mistakes and mix up the PDBs for different
targets in some situations. CMake allows the name of each target’s object PDB
file to be controlled with the COMPILE_PDB target property or the
associated configuration-specific COMPILE_PDB_<CONFIG> target properties.
The location of these object PDB files can also be overridden with the
COMPILE_PDB_OUTPUT_DIRECTORY and
COMPILE_PDB_OUTPUT_DIRECTORY_<CONFIG> target properties.
Note that these object PDB files are of little use for DLL and executable
targets, since the main PDB already contains all the debugging information
required.
项目生成器通常提供某种clean目标,可用于删除所有生成的文件、构建输出等。IDE 工具有时使用它来提供基本的重建功能,如清理后进行构建,或者由开发人员简单地使用删除构建输出以强制在下一次构建尝试时重建所有内容。有时,项目定义自定义规则的方式会创建 CMake 不知道的文件,因此它们不会包含在该clean步骤中,并且仍有可能影响下一个构建。
Project generators usually provide some kind of clean target that can be used
to remove all the generated files, build outputs, etc. This is sometimes used
by IDE tools to provide a basic rebuild feature as a clean followed by a build,
or by developers to simply remove build outputs to force rebuilding everything
on the next build attempt. Sometimes a project defines a custom rule in such a
way that it creates files that CMake doesn’t know about, so they are not
included in the clean step and have the potential to still affect the next
build.
有多种机制可以使文件成为要由clean操作删除的集合的一部分。Ninja 生成器会自动将任何生成的文件添加到集合中clean
。作为自定义命令或自定义目标列出的任何文件BYPRODUCTS也会被识别为生成的文件,并且也会添加到该clean集中。在 CMake 3.13 及更高版本中,Makefile 生成器还会将生成的文件和副产品文件添加到集合中clean。
There are a variety of mechanisms by which files can become part of the set to
be removed by a clean operation.
The Ninja generators will automatically add any generated files to the clean
set.
Any files listed as BYPRODUCTS of a custom command or custom target are
also recognized as generated and are added to the clean set as well.
With CMake 3.13 and later, Makefile generators also add generated and byproduct
files to the clean set.
对于要清理的其他文件,首选方法取决于最低 CMake 版本。对于 CMake 3.15 或更高版本,ADDITIONAL_CLEAN_FILES目录和目标属性可用于指定要清理的文件列表。作为构建特定目标的一部分创建的各种文件应添加到目标属性中。如果文件不与单个目标关联,则应将它们添加到目录属性中。只有 Ninja 和 Makefile 生成器支持这些ADDITIONAL_CLEAN_FILES
属性。如果需要支持 CMake 3.14 或更早版本,
ADDITIONAL_MAKE_CLEAN_FILES可以使用目录属性,但从 CMake 3.15 开始,它已被正式弃用。它的工作方式与目录属性相同ADDITIONAL_CLEAN_FILES,但仅受 Makefile 生成器支持。
For other files to be cleaned, the preferred approach depends on the minimum
CMake version.
For CMake 3.15 or later, the ADDITIONAL_CLEAN_FILES directory and
target properties can be used to specify a list of files to be cleaned.
Miscellaneous files created as part of building a particular target should be
added to the target property.
If files are not associated with a single target, they should be added to the
directory property instead.
Only the Ninja and Makefile generators support the ADDITIONAL_CLEAN_FILES
properties.
If needing to support CMake 3.14 or earlier, the
ADDITIONAL_MAKE_CLEAN_FILES directory property can be used instead, but
it is officially deprecated as of CMake 3.15.
It works the same way as the ADDITIONAL_CLEAN_FILES directory property, but
it is only supported by Makefile generators.
如果特定文件发生更改,某些更高级的技术可能需要重新运行 CMake。通常,CMake 在自动跟踪其控制的事物的依赖关系方面做得很好,例如使用
configure_file()命令复制文件,但自定义命令和其他任务可能依赖于 CMake 不知道依赖关系的文件。可以将此类文件添加到CMAKE_CONFIGURE_DEPENDS目录属性中,如果列出的任何文件发生更改,CMake 将在下一次构建之前重新运行。如果使用相对路径指定文件,则该文件将被视为相对于与目录属性关联的源目录。
Certain more advanced techniques may require CMake to be re-run if a particular
file changes. Normally, CMake does a good job of automatically tracking
dependencies for things it controls, such as copying files with the
configure_file() command, but custom commands and other tasks may rely on
files for which CMake isn’t aware of the dependency. Such files can be added to
the CMAKE_CONFIGURE_DEPENDS directory property and if any of the listed
files change, CMake will be re-run before the next build. If a file is
specified with a relative path, it will be taken to be relative to the source
directory associated with the directory property.
大多数项目通常不需要使用CMAKE_CONFIGURE_DEPENDS
目录属性,但它可以而且应该在 CMake 没有机会了解充当配置或生成步骤输入的文件的情况下使用。大多数文件依赖项是构建时依赖项,而不是配置或生成时依赖项,因此在使用此属性之前,请检查项目是否确实需要重新运行 CMake,而不是简单地重新编译源文件或目标作为常规构建的一部分。
Most projects won’t typically need to make use of the CMAKE_CONFIGURE_DEPENDS
directory property, but it can and should be used where CMake doesn’t have the
opportunity to know about files which act as input to the configure or
generation steps.
Most file dependencies are build time dependencies, not configure or
generation time, so before using this property, check whether the project
really does need to re-run CMake rather than simply recompiling a source file
or target as part of the regular build.
不可避免地,需要将来自某些外部源的项目添加到构建中,但它存在某种问题,导致其无法正常工作。一些常见的示例包括未设置应设置的变量或属性,或者假设它是顶级项目。当处理支持非常旧的 CMake 版本且尚未更新以处理较新的 CMake 功能和技术的项目时,这些事情尤其常见。对于其中一些问题,可以注入 CMake 代码,而无需实际修改外部项目并解决问题。
There will inevitably come a time where a project from some external source needs to be added to a build, but it has some sort of problem that prevents it from working correctly. Some common examples include not setting variables or properties that should have been set, or making the assumption it is the top level project. These things are especially common when working with projects that support very old CMake versions and have not been updated to handle newer CMake features and techniques. For some of these issues, it is possible to inject CMake code without having to actually modify the external project and work around the problem.
该project()命令有一个功能,它将检查名称CMAKE_PROJECT_<PROJNAME>_INCLUDE为该命令<PROJNAME>的项目名称的变量project()。如果定义了该变量,则假定它保存 CMake 应包含的文件名,作为命令project()返回之前执行的最后一件事。实际上,该project()命令的工作原理如下:
The project() command has a feature whereby it will check for a variable with
the name CMAKE_PROJECT_<PROJNAME>_INCLUDE where <PROJNAME> is the
project name as given to the project() command.
If that variable is defined, it is assumed to hold the name of a file that
CMake should include as the last thing the project() command does before
returning. In effect, the project() command works like this:
project(SomeProj)
if(DEFINED CMAKE_PROJECT_SomeProj_INCLUDE)
include(${CMAKE_PROJECT_SomeProj_INCLUDE})
endif()project(SomeProj)
if(DEFINED CMAKE_PROJECT_SomeProj_INCLUDE)
include(${CMAKE_PROJECT_SomeProj_INCLUDE})
endif()
使用 CMake 3.15 或更高版本时,无论给定的项目名称如何,都可以在每个命令之前注入两个变量。 project()这些变量CMAKE_PROJECT_INCLUDE_BEFORE允许
分别CMAKE_PROJECT_INCLUDE在正常命令处理之前或之后插入代码
project()。它们在项目名称可能不容易知道的情况下最有用,例如当构建由在多个项目构建中重复使用的通用脚本驱动时。考虑到这些附加变量的扩展行为等效于以下内容:
When using CMake 3.15 or later, two more variables are available to inject
files before every project() command, regardless of the project name given.
These variables, CMAKE_PROJECT_INCLUDE_BEFORE and
CMAKE_PROJECT_INCLUDE allow code to be inserted before or after normal
project() command processing respectively.
They are mostly useful for situations where the project name may not be readily
known, such as when a build is driven by generic scripts that are re-used
across multiple project builds.
The expanded behavior accounting for these additional variables is equivalent
to the following:
# CMake 3.15 or later only
if(DEFINED CMAKE_PROJECT_INCLUDE_BEFORE)
include(${CMAKE_PROJECT_INCLUDE_BEFORE})
endif()
project(SomeProj)
# CMake 3.15 or later only
if(DEFINED CMAKE_PROJECT_INCLUDE)
include(${CMAKE_PROJECT_INCLUDE})
endif()
# All CMake versions
if(DEFINED CMAKE_PROJECT_SomeProj_INCLUDE)
include(${CMAKE_PROJECT_SomeProj_INCLUDE})
endif()# CMake 3.15 or later only
if(DEFINED CMAKE_PROJECT_INCLUDE_BEFORE)
include(${CMAKE_PROJECT_INCLUDE_BEFORE})
endif()
project(SomeProj)
# CMake 3.15 or later only
if(DEFINED CMAKE_PROJECT_INCLUDE)
include(${CMAKE_PROJECT_INCLUDE})
endif()
# All CMake versions
if(DEFINED CMAKE_PROJECT_SomeProj_INCLUDE)
include(${CMAKE_PROJECT_SomeProj_INCLUDE})
endif()
由于每个调用都支持此行为project(),project()
因此每个调用都成为 CMake 代码注入的潜在点。它可用于更改项目内目标属性的默认值、添加编译器或链接器标志等。此功能的另一个特别方便的用途是安全地设置持续集成构建的选项,而无需将它们保存在 CMake 缓存中。这意味着增量构建不太可能受到旧的 CMake 缓存选项的影响,这些选项在项目的后续更改后被删除或不再设置。
Because this behavior is supported for every project() call, each project()
call therefore becomes a potential point of CMake code injection.
It can be used to change the defaults for target properties within the project,
add compiler or linker flags and so on.
Another particularly handy use of this feature is to safely set options for
continuous integration builds without having to save them in the CMake cache.
This means incremental builds are less likely to be affected by old CMake cache
options that are removed or no longer set after subsequent changes to the
project.
例如,考虑一个开发人员在集成分支上工作,其中应临时启用额外检查。一种幼稚的方法是显式设置变量,例如CMAKE_C_FLAGS或CMAKE_CXX_FLAGS,但由于 CI 脚本不应更改项目本身,因此唯一的选择是将它们设置为缓存选项。合并分支时,这些缓存选项将继续存在用于增量构建,但不应再应用它们。唯一的做法是清除缓存,这可能会强制完全重建。更好的替代方法是
CMAKE_PROJECT_<PROJNAME>_INCLUDE在最顶层project()调用结束时用于处理特定于 CI 的文件。该文件将像项目的其余部分一样受到源代码控制。在合并分支之前,该文件将恢复为其正常内容,并且构建不会保留临时标志。
For example, consider a developer working on an integration branch where extra
checks should be enabled temporarily. A naive approach would be to explicitly
set variables like CMAKE_C_FLAGS or CMAKE_CXX_FLAGS, but since the CI
scripts shouldn’t change the project itself, the only choice would be to set
them as cache options. When the branch is merged, those cache options will
continue to be present for incremental builds, but they should no longer be
getting applied. The only course of action then is to clear the cache which
would likely force a complete rebuild. A better alternative is to use
CMAKE_PROJECT_<PROJNAME>_INCLUDE to process a CI-specific file at the end of
the top-most project() call. This file would be under source control just
like the rest of the project. Before the branch is merged, that file would be
restored to its normal contents and the build would not retain the temporary
flags.
cmake_minimum_required(VERSION 3.0)
project(MyProj)
...cmake_minimum_required(VERSION 3.0)
project(MyProj)
...
CI 系统将像这样调用 CMake:
CMake would be invoked like so by the CI system:
cmake -D CMAKE_PROJECT_MyProj_INCLUDE:FILEPATH=path/to/ciOptions.cmake ...
cmake -D CMAKE_PROJECT_MyProj_INCLUDE:FILEPATH=path/to/ciOptions.cmake ...
通常,该文件ciOptions.cmake可能为空或仅包含一些常见设置,例如打开可选功能。对于分支,它可能包含如下内容:
Ordinarily, the file ciOptions.cmake might be empty or just contain a few
common settings such as turning on optional features. For the branch, it
might contain things like this:
compile_definitions(DO_EXTRA_CI_CHECKS=1)
set(ENABLE_SANITIZERS YES)compile_definitions(DO_EXTRA_CI_CHECKS=1)
set(ENABLE_SANITIZERS YES)
像这样将文件注入到project()命令中不应该是项目正常开发的一部分。它具有克服旧项目中的缺陷和非常受控的情况(例如持续集成构建)的特定用途。除了这些情况之外,开发人员通常更喜欢CMakeLists.txt直接添加或修改项目的文件。
Injecting files into project() commands like this should not be part of the
normal development of a project. It has specific uses for overcoming
deficiencies in older projects and for very controlled situations such as in
continuous integration builds. Outside of those cases, developers should
generally prefer to add or modify the project’s CMakeLists.txt files
directly.
随着开发人员对 CMake 的经验越来越丰富,某些使用模式和场景往往会重复。一些常见的例子包括:
As a developer becomes more experienced with CMake, certain usage patterns and scenarios tend to be repeated. Some common examples include:
这些都是与为项目设置新的构建目录相关的事情。开发人员有时会创建脚本来自动化该过程,或者他们可能会使用未记录的 CMake 功能来预加载 CMake 缓存。虽然这些措施可能是一种改进,但它们并不总是可靠或方便的。
These are all things that relate to setting up a fresh build directory for a project. Developers will sometimes create scripts to automate the process, or they might use undocumented CMake features to pre-load the CMake cache. While these measures may be an improvement, they are not always robust or convenient.
CMake 3.19 添加了一个称为预设的新功能,它允许以更可靠和方便的方式处理上述场景。预设可以指定构建目录、CMake 生成器、目标架构、主机工具集、CMake 变量、环境变量甚至特定于供应商的内容(例如,对于某些 IDE 工具)。这些会影响开发人员工作流程的配置阶段。CMake 3.20 进一步扩展了预设功能,添加了对影响构建和测试阶段的部分的支持。
CMake 3.19 added a new feature called presets which allow scenarios like those above to be handled in a more reliable and convenient way. Presets can specify the build directory, CMake generator, target architecture, host toolset, CMake variables, environment variables and even vendor-specific content (e.g. for certain IDE tools). These affect the configure phase of the developer workflow. CMake 3.20 extended the presets functionality further, adding support for sections that affect the build and test phases.
CMake 在项目的顶级源目录中查找名为
CMakePresets.json和的文件CMakeUserPresets.json。这两个文件都不需要存在,但如果用户尝试列出或使用预设,则至少其中一个文件必须存在。如果两个文件都存在,则通过
CMakePresets.json先读取文件,然后再CMakeUserPresets.json读取文件,它们将被有效地合并。这两个文件的格式完全相同,但用途不同:
CMake looks in the top source directory of a project for files named
CMakePresets.json and CMakeUserPresets.json.
Neither file is required to be present, but if the user tries to list or use
presets, at least one of the files must exist.
If both files are present, they will effectively be merged by reading the
CMakePresets.json file first and then the CMakeUserPresets.json file.
The two files have exactly the same format, but they serve different purposes:
CMakePresets.json
CMakePresets.json
CMakeUserPresets.json
CMakeUserPresets.json
CMakeUserPresets.json文件,并且最好将其排除在源代码控制之外。开发人员可能希望将此文件名添加到其全局 git 忽略配置或其他等效的源代码控制设置中。
CMakeUserPresets.json file and it should
ideally be excluded from source control.
Developers may want to add this file name to their global git ignore
configuration or other equivalent source control settings.
CMake 文档包含预设文件格式的详细说明。以下示例显示了最重要的顶级元素:
The CMake documentation includes a detailed explanation of the format of preset files. The following example shows the most important top level elements:
{
"version": 2,
"configurePresets": [...],
"buildPresets": [...],
"testPresets": [...]
}{
"version": 2,
"configurePresets": [...],
"buildPresets": [...],
"testPresets": [...]
}
该version字段指定 JSON 模式并且必须始终存在。版本 1 是第一个版本,仅支持configurePresets. 版本 2 需要 CMake 3.20 或更高版本,并添加了对buildPresets和 的
支持testPresets。版本 3 添加了其他功能,并且需要 CMake 3.21 或更高版本。
The version field specifies the JSON schema and must always be present.
Version 1 is the first release and only supports configurePresets.
Version 2 requires CMake 3.20 or later and adds support for buildPresets and
testPresets.
Version 3 adds other capabilities and requires CMake 3.21 or later.
配置预设适用于开发人员工作流程的配置阶段。运行 CMake 时可以使用它们来为项目设置构建目录。为了使预设文件有用,它需要提供一个configurePresets
包含一个或多个预设数组的部分。一些关键示例说明了最重要的元素和基本用法。
Configure presets apply to the configure phase of the developer workflow.
They can be used when running CMake to set up a build directory for a project.
For a preset file to be useful, it needs to provide a configurePresets
section containing an array of one or more presets.
A few key examples illustrate the most important elements and essential usage.
{
"version": 1,
"configurePresets": [
{
"name": "ninja",
"displayName": "Ninja Debug",
"generator": "Ninja",
"binaryDir": "build-debug",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
}
]
}{
"version": 1,
"configurePresets": [
{
"name": "ninja",
"displayName": "Ninja Debug",
"generator": "Ninja",
"binaryDir": "build-debug",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
}
]
}
每个预设都必须提供一个name. 它是一个单词,用作预设的唯一名称。AdisplayName是可选的,但推荐使用。它显示在组合框等 GUI 应用程序中。所有可用(非隐藏)配置预设的name和displayName(如果已定义)可以通过从项目的顶级源目录运行以下命令来获取:
Every preset must provide a name.
It is a single word that serves as the unique name of the preset.
A displayName is optional, but recommended.
It is shown in GUI applications in combo boxes, etc.
The name and displayName (if defined) of all available (non-hidden)
configure presets can be obtained by running the following command from the
top source directory of a project:
cmake --列表预设 可用的配置预设: “ninja” - 忍者调试
cmake --list-presets Available configure presets: "ninja" - Ninja Debug
CMake GUI 还显示当前所选源目录的可用配置预设。预设按其列出,如果未
提供,则displayName返回到。该工具不支持预设。namedisplayNameccmake
The CMake GUI also shows the available configure presets for the currently
selected source directory.
Presets are listed by their displayName, falling back to the name if no
displayName is provided.
The ccmake tool does not support presets.
用于name在命令行上选择配置预设cmake。当从项目的顶级源目录运行时,以下命令将选择上面定义的配置预设:
The name is used to select a configure preset on the cmake command line.
When run from the top source directory of the project, the following would
select the configure preset defined above:
cmake --preset=忍者
cmake --preset=ninja
对于版本 1 或 2,配置预设还必须提供 和generator,
binaryDir尽管这些可以继承(请参阅下文)。版本 3 允许省略generator和binaryDir,在这种情况下,它们遵循与不使用预设时相同的行为。
With version 1 or 2, configure presets must also provide a generator and
binaryDir, although these can be inherited (see further below).
Version 3 allows the generator and binaryDir to be omitted, in which case
they follow the same behavior as when presets are not used.
如果配置预设提供了一个字段(
如果是版本 1 或 2 预设文件,binaryDir则还提供一个字段),则该选项本身足以配置构建目录。任何常用的 CMake 选项仍然可以在命令行上提供,并且将覆盖相关的预设。例如,上面的示例定义了缓存变量。其他缓存变量可以使用命令行上的选项添加,甚至覆盖.generator--preset=…cmakeCMAKE_BUILD_TYPE-DCMAKE_BUILD_TYPE
If the configure preset provides a binaryDir field (and also a generator
field if it is a version 1 or 2 presets file), the --preset=… option is
enough on its own to configure a build directory.
Any of the usual CMake options can still be provided on the cmake command
line too and will override the preset, where relevant.
For instance, the above example defines the CMAKE_BUILD_TYPE cache variable.
Other cache variables can be added with -D options on the command line, or
even override the CMAKE_BUILD_TYPE.
缓存变量可以以简单的key:value形式定义,如上例所示,也可以定义为 JSON 对象。非对象形式会将变量定义为类型,STRING除非该值是不带引号的true或false,在这种情况下它将是类型BOOL。对象形式允许指定变量类型,这可以改进变量在 GUI 应用程序中的呈现方式。例如:
Cache variables can be defined in the simple key:value form as shown in the
above example, or they can be defined as a JSON object.
The non-object form will define the variable as type STRING unless the value
is an unquoted true or false, in which case it will be of type BOOL.
The object form allows the variable type to be specified, which can improve
the way the variable is presented in GUI applications.
For example:
{
"version": 1,
"configurePresets": [
{
"name": "ninja",
"generator": "Ninja",
"binaryDir": "build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": {
"type": "FILEPATH",
"value": "/path/to/toolchain.cmake"
}
}
}
]
}{
"version": 1,
"configurePresets": [
{
"name": "ninja",
"generator": "Ninja",
"binaryDir": "build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": {
"type": "FILEPATH",
"value": "/path/to/toolchain.cmake"
}
}
}
]
}
预设可以继承一个或多个相同类型(配置、构建或测试)的其他预设,以添加或覆盖字段。这可用于从常见构建块组成预设,从而避免重复并减少错误。重要的是, 中定义的预设CMakeUserPresets.json可以继承 中定义的预设CMakePresets.json,但反之则不然。预设也可以隐藏,表明它仅存在,以便其他预设可以继承它。
Presets can inherit from one or more other presets of the same type
(configure, build or test) to add or override fields.
This can be used to compose presets from common building blocks, thereby
avoiding duplication and reducing errors.
Importantly, presets defined in CMakeUserPresets.json can inherit from those
defined in CMakePresets.json, but not vice versa.
A preset can also be hidden, indicating it only exists so that other presets
can inherit from it.
{
"version": 1,
"configurePresets": [
{
"name": "ci-enable-all",
"hidden": true,
"binaryDir": "build",
"cacheVariables": {
"MYPROJ_FEATURE_X": true,
"MYPROJ_FEATURE_Y": true
}
},
{
"name": "ci-linux",
"inherits": "ci-enable-all",
"displayName": "Linux continuous integration setup",
"generator": "Ninja",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
},
{
"name": "ci-macos",
"inherits": "ci-enable-all",
"displayName": "macOS continuous integration setup",
"generator": "Xcode"
}
]
}
{
"version": 1,
"configurePresets": [
{
"name": "ci-enable-all",
"hidden": true,
"binaryDir": "build",
"cacheVariables": {
"MYPROJ_FEATURE_X": true,
"MYPROJ_FEATURE_Y": true
}
},
{
"name": "ci-linux",
"inherits": "ci-enable-all",
"displayName": "Linux continuous integration setup",
"generator": "Ninja",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
},
{
"name": "ci-macos",
"inherits": "ci-enable-all",
"displayName": "macOS continuous integration setup",
"generator": "Xcode"
}
]
}
{
"version": 1,
"configurePresets": [
{
"name": "sibling-build-dir",
"hidden": true,
"binaryDir": "${sourceParentDir}/build/${sourceDirName}"
},
{
"name": "ninja",
"inherits": ["sibling-build-dir", "ci-enable-all"],
"displayName": "Ninja Debug",
"generator": "Ninja",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
}
]
}
{
"version": 1,
"configurePresets": [
{
"name": "sibling-build-dir",
"hidden": true,
"binaryDir": "${sourceParentDir}/build/${sourceDirName}"
},
{
"name": "ninja",
"inherits": ["sibling-build-dir", "ci-enable-all"],
"displayName": "Ninja Debug",
"generator": "Ninja",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
}
]
}
该CMakePresets.json文件定义了一个基本配置预设,
ci-enable-all该文件中的其他两个配置预设都继承自该预设。这允许共享一组通用的缓存选项,而binaryDir不必在各处重复它们。和配置预设只需要定义特定于自己的生成器和平台ci-linux的ci-macos信息。
The CMakePresets.json file defines a base configure preset called
ci-enable-all which the other two configure presets in that file both
inherit from.
This allows the common set of cache options and the binaryDir to be shared
without having to repeat them everywhere.
The ci-linux and ci-macos configure presets only need to define the
information specific to their own generator and platform.
该CMakeUserPresets.json文件展示了如何继承多个预设,包括CMakePresets.json. 和 都ci-enable-all定义sibling-build-dir了一个binaryDir. 在多个基本预设定义同一字段且派生预设不覆盖它的情况下,列表中较早出现的预设
inherits优先。在这种情况下,对于ninja预设,
使用binaryDirfrom因为它出现在 之前。如果预设本身定义了 a ,那么它将覆盖其他预设。sibling-build-dirci-enable-allninjabinaryDir
The CMakeUserPresets.json file shows how to inherit from multiple presets,
including one defined in CMakePresets.json.
Both ci-enable-all and sibling-build-dir define a binaryDir.
In such cases where multiple base presets define the same field and the
derived preset doesn’t override it, the one that appears earlier in the
inherits list takes precedence.
In this case, for the ninja preset, the binaryDir from sibling-build-dir
is used because it appears before ci-enable-all.
If the ninja preset itself defined a binaryDir, that would override the
others.
该示例演示的另一个功能CMakeUserPresets.json是使用以下形式的宏${macroName}。这些计算值的计算结果与 CMake 变量的工作方式非常相似。CMake 文档提供了可用宏名称以及支持它们的字段的完整列表。一些更有用的宏名称包括:
Another feature demonstrated by the CMakeUserPresets.json example is the use
of macros of the form ${macroName}.
These evaluate to a computed value much like how CMake variables work.
The CMake documentation provides the full list of available macro names and
the fields that support them.
Some of the more useful macro names include:
presetName
presetName
generator
generator
sourceDir
sourceDir
sourceParentDir
sourceParentDir
sourceDirName
sourceDirName
sourceDir,即最后一个路径分隔符之后的部分。
sourceDir, i.e. the part after the last
path separator.
hostSystemName
hostSystemName
CMAKE_HOST_SYSTEM_NAME。需要版本 3 或更高版本。
CMAKE_HOST_SYSTEM_NAME variable.
Requires version 3 or higher.
CMakeUserPresets.json上面示例中的 展示了如何
使用${sourceParentDir}和${sourceDirName}使预设文件更加通用。如果在多个项目中重复使用它(例如,通过将其复制或符号链接到每个项目的源目录中),这可能很重要。
${sourceParentDir}允许将构建目录放置在项目源目录之外,如第 2.2 节“源外构建”中的建议。当所有项目源都是彼此的同级时,这可以方便地将所有构建目录收集在一个位置下。
${sourceDirName}提供特定于项目的部分,确保每个项目的构建目录都是唯一的。
The CMakeUserPresets.json in the above example shows how
${sourceParentDir} and ${sourceDirName} can be used to make a preset file
more generic.
This could be important if re-using it across multiple projects (e.g. by
copying or symlinking it into each project’s source directory).
${sourceParentDir} allows the build directories to be placed outside the
project source directory, as recommended in Section 2.2, “Out-of-source Builds”.
When all project sources are siblings of each other, this conveniently
collects all build directories under a single location.
${sourceDirName} provides the project-specific part that ensures each
project’s build directory is unique.
请注意所有预设的名称都有CMakePresets.json一个
ci-前缀。好的做法是选择预设名称,因为CMakePresets.json它不太可能与开发人员在自己的
CMakeUserPresets.json文件中定义的名称发生冲突。由于持续集成设置是项目应该定义的内容,因此ci-前缀通常是一个不错的选择。这也清楚地传达了预设的预期用例。使用包含项目名称的前缀是另一种潜在的策略。
Note how the names given to the presets in CMakePresets.json all have a
ci- prefix.
Good practice is to choose preset names in CMakePresets.json that would be
unlikely to clash with names a developer might define in their own
CMakeUserPresets.json file.
Since continuous integration setup is something that the project should define,
a ci- prefix is typically a good choice.
This also clearly communicates the intended use case for the presets.
Using a prefix that incorporates the name of the project is another potential
strategy.
配置预设可以使用地图修改配置步骤的环境
environment。除非使用构建预设(下面进一步讨论),否则这不会继续到构建步骤。环境变量也可以在支持宏的字段中使用$env{varName}或
进行评估。$penv{varName}两者之间的唯一区别是$penv{varName}始终提供来自父环境的值,而$env{varName}会考虑预设对环境所做的更改。
$penv{varName}当环境变量的当前值需要包含在新值中时,这是必要的,就像PATH. 环境变量也可以被赋予值null以使其被取消设置。
Configure presets can modify the configure step’s environment with an
environment map.
This does not carry forward to the build step unless a build preset is used
(discussed further below).
Environment variables can also be evaluated with either $env{varName} or
$penv{varName} in fields where macros are supported.
The only difference between the two is that $penv{varName} always provides
the value from the parent environment, whereas $env{varName} will take into
account changes made to the environment by the preset.
$penv{varName} is necessary when the current value of an environment
variable needs to be included in the new value, as is common for variables
like PATH.
An environment variable can also be given the value null to cause it to be
unset.
{
"version": 1,
"configurePresets": [
{
"name": "ninja",
"generator": "Ninja",
"binaryDir": "build",
"environment": {
"PATH": "${sourceDir}/scripts:$penv{PATH}",
"PATH_COPY": "$env{PATH}",
"CLEAR_ME_PLEASE": null
}
}
]
}{
"version": 1,
"configurePresets": [
{
"name": "ninja",
"generator": "Ninja",
"binaryDir": "build",
"environment": {
"PATH": "${sourceDir}/scripts:$penv{PATH}",
"PATH_COPY": "$env{PATH}",
"CLEAR_ME_PLEASE": null
}
}
]
}
当一个预设继承另一个预设并且它们都定义一个 时environment,结果是继承层次结构中所有预设的合并并集。如果多个预设定义相同的环境变量,则所采用的值遵循前面所述的相同继承规则(基值将被派生预设覆盖,较早继承的预设优先于列表中较晚的预设inherits)。也使用类似的合并行为cacheVariables。
When a preset inherits from another and they both define an environment, the
result is a merged union of all presets in the inheritance hierarchy.
If multiple presets define the same environment variable, the value taken
follows the same inheritance rules as described earlier (base values are
overridden by derived presets, earlier inherited presets take precedence over
later ones in the inherits list).
Similar merging behavior is used for cacheVariables as well.
当配置预设使用 Visual Studio 生成器时,它可能还需要指定体系结构或主机工具集。为此提供了专用字段,可以按以下示例使用:
When a configure preset uses a Visual Studio generator, it may also want to specify an architecture or host toolset. Dedicated fields are provided for this and can be used as in the following example:
{
"version": 1,
"configurePresets": [
{
"name": "vs2019-arm64",
"displayName": "Visual Studio 2019 - ARM64",
"generator": "Visual Studio 16 2019",
"architecture": "ARM64",
"toolset": "host=x86",
"binaryDir": "build-arm64"
}
]
}{
"version": 1,
"configurePresets": [
{
"name": "vs2019-arm64",
"displayName": "Visual Studio 2019 - ARM64",
"generator": "Visual Studio 16 2019",
"architecture": "ARM64",
"toolset": "host=x86",
"binaryDir": "build-arm64"
}
]
}
版本 3 架构支持一个condition对象,该对象可用于启用基于某些规则的预设。例如,带有 Xcode 的预设generator仅在 macOS 计算机上有用。条件可以是常量、字符串相等、不等、正则表达式匹配或检查列表是否包含特定值。布尔逻辑也可以使用allOf,anyOf和not
关系来表达。官方 CMake 文档相当全面地定义了支持的条件,但以下示例给出了一些典型用途的想法:
The version 3 schema supports a condition object, which can be used to
enable a preset based on certain rules.
For example, a preset with a Xcode generator would only be useful on a
macOS machine.
The condition can be a constant, string equality, inequality, regular
expression match or a check for whether a list contains a particular value.
Boolean logic can also be expressed using allOf, anyOf and not
relationships.
The official CMake documentation defines the supported conditions fairly
comprehensively, but the following examples give an idea of some typical uses:
{
"version": 3,
"configurePresets": [
{
"name": "default-generator",
"binaryDir": "build"
},
{
"name": "xcode",
"generator": "Xcode",
"binaryDir": "build-xcode",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
}
}
]
}{
"version": 3,
"configurePresets": [
{
"name": "default-generator",
"binaryDir": "build"
},
{
"name": "xcode",
"generator": "Xcode",
"binaryDir": "build-xcode",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
}
}
]
}
{
"version": 3,
"configurePresets": [
{
"name": "package-release",
"generator": "Ninja",
"binaryDir": "build",
"condition": {
"type": "allOf",
"conditions" : [
{
"type": "equals",
"lhs": "$penv{CI_COMMIT_REF_PROTECTED}",
"rhs": "true"
},
{
"type": "matches",
"string": "$penv{CI_COMMIT_TAG}",
"regex": "release/.*"
}
]
}
}
]
}{
"version": 3,
"configurePresets": [
{
"name": "package-release",
"generator": "Ninja",
"binaryDir": "build",
"condition": {
"type": "allOf",
"conditions" : [
{
"type": "equals",
"lhs": "$penv{CI_COMMIT_REF_PROTECTED}",
"rhs": "true"
},
{
"type": "matches",
"string": "$penv{CI_COMMIT_TAG}",
"regex": "release/.*"
}
]
}
}
]
}
前面的讨论主要集中configurePresets在配置阶段。它们buildPresets在运行构建工具时提供类似的功能。开发人员可以选择在构建时使用的构建预设,如下所示:
The preceding discussion focused on configurePresets, which apply to the
configure phase.
The buildPresets provide a similar capability for when running the build
tool.
The developer can select a build preset to use at build time like so:
cmake --build --预设名称
cmake --build --preset name
构建预设支持许多与配置预设相同的字段,例如
name、displayName、inherits和。其中每一个的工作方式都与配置预设相同。此外,构建预设必须在名为 的字段中指定(或继承)配置预设的名称,该字段用于确定构建目录的位置。默认情况下,构建预设还会继承配置预设中的任何环境,尽管这可以禁用。以下示例演示了这些功能:hiddenenvironmentconfigurePreset
Build presets support many of the same fields as configure presets, such as
name, displayName, inherits, hidden and environment.
Each of these work the same way as for configure presets.
In addition, a build preset must specify (or inherit) the name of a configure
preset in a field called configurePreset, which is used to determine the
location of the build directory.
A build preset also inherits any environment from the configure preset by
default, although this can be disabled.
The following example demonstrates these capabilities:
{
"version": 2,
"configurePresets": [
{
"name": "ninja",
"generator": "Ninja Multi-Config",
"binaryDir": "build",
"environment": {
"PATH": "/tools/dir:$penv{PATH}"
}
}
],
"buildPresets" : [
{
"name": "base",
"hidden": true,
"configurePreset": "ninja",
"configuration": "Release"
},
{
"name": "devtools",
"inherits": "base",
"inheritConfigureEnvironment": false,
"targets": ["hexdump", "logger"]
},
{
"name": "alldocs",
"inherits": "base",
"targets": ["manual", "api", "quickstart"]
}
]
}{
"version": 2,
"configurePresets": [
{
"name": "ninja",
"generator": "Ninja Multi-Config",
"binaryDir": "build",
"environment": {
"PATH": "/tools/dir:$penv{PATH}"
}
}
],
"buildPresets" : [
{
"name": "base",
"hidden": true,
"configurePreset": "ninja",
"configuration": "Release"
},
{
"name": "devtools",
"inherits": "base",
"inheritConfigureEnvironment": false,
"targets": ["hexdump", "logger"]
},
{
"name": "alldocs",
"inherits": "base",
"targets": ["manual", "api", "quickstart"]
}
]
}
列出上述示例的可用构建预设,可以确认从命令行有两个可用的构建预设:
Listing the available build presets for the above example, it can be confirmed that two build presets are available from the command line:
cmake --build --list-presets
cmake --build --list-presets
可用的构建预设: “开发工具” “所有文档”
Available build presets: "devtools" "alldocs"
这两个构建预设都继承了隐藏的base构建预设,而隐藏的构建预设又与ninja配置预设关联。结果是 和devtools都alldocs将与配置预设定义的构建目录相关联ninja。构建devtools预设还明确放弃配置预设定义的环境更改。
Both of these build presets inherit the hidden base build preset, which in
turn is associated with the ninja configure preset.
The result is that both devtools and alldocs will be associated with
the build directory defined by the ninja configure preset.
The devtools build preset also explicitly discards the environment changes
defined by the configure preset.
许多其他命令行选项也可以表示为构建预设的一部分。例如,上面的示例将Release配置指定为构建预设的一部分,然后也由和
base继承。CMake 文档指定了可用字段的完整列表以及每个字段对应的命令行选项。devtoolsalldocs
Many other command line options can also be expressed as part of a build
preset.
For instance, the above example specifies the Release configuration as part
of the base build preset, which is then inherited by devtools and
alldocs too.
The CMake documentation specifies the full list of available fields and the
command line option each one corresponds to.
测试预设遵循与构建预设非常相似的模式,只不过它们应用于以下调用ctest:
Test presets follow a very similar pattern to build presets, except they are
applied to invocations of ctest:
ctest --预设名称 ctest --列表预设
ctest --preset name ctest --list-presets
测试预设支持相同的常用name、displayName、inherits和
字段,以及映射到各种
命令行选项的hidden其他字段。它们可以指定常用的测试夹具设置组合、用于选择或排除测试的正则表达式、测试输出选项等。下面说明了如何使用其中一些功能:environmentctest
Test presets support the same common name, displayName, inherits,
hidden and environment fields, plus others that map to various ctest
command line options.
They can specify commonly used combinations of test fixture settings, regular
expressions for selecting or excluding tests, test output options and so on.
The following illustrates how some of these features can be used:
{
"version": 2,
"configurePresets": [
{
"name": "ninja", ...
}
],
"testPresets" : [
{
"name": "mytests",
"configurePreset": "ninja",
"filter": {
"include": {
"name": "SomeFeature"
},
"exclude": {
"label": "Slow"
}
}
}
]
}{
"version": 2,
"configurePresets": [
{
"name": "ninja", ...
}
],
"testPresets" : [
{
"name": "mytests",
"configurePreset": "ninja",
"filter": {
"include": {
"name": "SomeFeature"
},
"exclude": {
"label": "Slow"
}
}
}
]
}
本节中的材料仅涵盖使用预设的基础知识。最好的学习方法是尝试现实世界的项目、尝试不同的想法并探索如何使用各种功能。请参阅 CMake 文档中的预设手册,全面了解所有可用功能。
The material in this section only covered the basics of using presets. They are best learned by experimenting with real world projects, trying out different ideas and exploring how the various capabilities can be used. Consult the presets manual in the CMake documentation for a comprehensive coverage of all the available features.
项目的构建和使用方式可能有很大差异。一些曾经司空见惯的事情现在被认为是糟糕的做法,因为新的功能和经验教训使得旧的方法可以被更强大、更灵活的新方法所取代,并且可以实现以前不可能的事情。工具升级、语言发展、依赖关系发生变化——所有这些都意味着项目也需要随着时间的推移进行调整。特别是对于 CMake 项目,那些继续以 3.0 之前的旧 CMake 版本为目标的项目将越来越面临坎坷的道路。人们正在大力转向以目标为中心的模型,并且 CMake 的大部分开发都是朝着这个方向发展的。因此,最好设置一个允许项目使用这些功能的最低 CMake 版本。低于 CMake 3.1 的任何版本都可能限制过多,因此由于更新的语言支持和新功能,因此至少考虑 CMake 3.7。如果使用 CUDA 等较新的工具或最新的语言标准,强烈建议使用最新的 CMake 版本。新版本的 Visual Studio 或 Xcode 也往往需要最新的 CMake 版本,以便针对这些工具链中的更改进行修复和添加。
The way projects are structured and used can vary considerably. Some things that used to be commonplace are now considered poor practice, as new features and lessons learned allow older methods to be replaced by newer ones that are more robust, more flexible and allow things that were not possible previously. Tools are upgraded, languages evolve, dependencies change - all of these things mean that projects will also need to adapt over time. For CMake projects in particular, those that continue to target older CMake versions before 3.0 will increasingly face a bumpy path. There is a strong move toward a target-centric model and much of CMake’s development is geared in that direction. Therefore, prefer to set a minimum CMake version that allows the project to make use of those features. Anything less than CMake 3.1 is likely to be too restrictive, so consider at least CMake 3.7 where possible due to the updated language support and new features. If working with newer tools like CUDA or a very recent language standard, the latest CMake release is strongly advised. New releases of Visual Studio or Xcode also tend to require recent CMake versions in order to pick up fixes and additions for changes in those toolchains.
每个项目需要做出的一个基本选择是将自身构建为超级构建还是常规构建。如果项目可以设置最低 CMake 版本 3.11,则非超级构建安排具有更强大的功能可用于依赖项管理,这可能不需要超级构建。考虑该FetchContent模块以及将本地导入目标推广到全球范围是否为开发人员提供了更大的灵活性和更好的体验。如果项目的所有依赖项都相对成熟并且具有明确定义的安装规则,则超级构建可能仍然是一个合适的替代方案,并且具有可以与更旧的 CMake 版本一起使用的优点。这两种方法都有其用处,但在项目生命周期的早期就决定是否使用超级建筑,项目就越有可能避免以后大规模的破坏性重组。
A fundamental choice that every project needs to make is whether to structure
itself as a superbuild or as a regular build. If the project can set a minimum
CMake version of 3.11, the non-superbuild arrangement has more powerful
features available to it for dependency management which may make the need for
a superbuild unnecessary. Consider whether the FetchContent module and the
promotion of local imported targets to global scope offer more flexibility and
a better experience for developers. Where all dependencies of a project are
relatively mature and have well defined install rules, a superbuild may still
be a suitable alternative and comes with the advantage that it can be used with
much older CMake versions. Both methods have their place, but the earlier in a
project’s life that the decision can be made on whether or not to use a
superbuild, the more likely the project can avoid large scale disruptive
restructuring later on.
无论项目是否是超级建筑,目标都是使项目的顶层重点关注更高级别的细节。将顶级CMakeLists.txt文件视为更像是项目的目录。顶级目录应该主要包含管理文件和一组子目录,每个子目录都专注于特定区域。避免使用可能导致与自动在构建目录中创建的子目录名称发生冲突的子目录名称。相反,最好使用相当标准的名称,除非存在必须遵循的现有约定。对于常规项目,目标是使顶级CMakeLists.txt文件遵循以下公共部分模式:
Irrespective of whether a project is a superbuild or not, aim to keep the top
level of the project focused on the higher level details. Think of the top
level CMakeLists.txt file as being more like a table of contents for the
project. The top level directory should mostly just contain administrative
files and a set of subdirectories each focused on a particular area. Avoid
subdirectory names that may cause clashes with those created in the build
directory automatically. Prefer instead to use fairly standard names unless
there is an existing convention that must be followed.
For regular projects, aim to make the top level CMakeLists.txt file follow
the common section pattern of:
用注释块清楚地描述每个部分将有助于鼓励项目开发人员维护该结构。跨项目建立这种模式将有助于加强对保持顶级CMakeLists.txt文件精简并充当高级概述的关注。
Clearly delineating each section with comment blocks will help encourage
developers working on the project to maintain that structure. Establishing this
pattern across projects will help reinforce the focus on keeping the top
level CMakeLists.txt file streamlined and acting as a high level overview.
当定义源跨目录分布的构建目标时,最好先创建目标,然后让每个子目录使用
target_sources(). 在适当的情况下,按功能或特性对子目录进行分组,以便它们可以作为一个单元轻松移动或启用/禁用。在许多情况下,其他以目标为中心的命令(即target_compile_definitions()、target_compile_options()和
target_include_directories())也可以在它们相关的子目录中本地使用。这有助于将信息保留在相关位置附近,而不是将其分散到各个目录中。避免使用变量来构建源列表,这些源列表将通过目录层次结构向上传递,并最终用于创建目标、定义编译器标志等。使用变量而不是直接对目标进行操作要脆弱得多、更冗长且更少可能会导致 CMake 捕获拼写错误或其他错误。
When defining build targets that have sources spread across directories, prefer
to create the target first, then have each subdirectory add sources to it using
target_sources(). Where appropriate, group the subdirectories by
functionality or feature so that they can be easily moved around or
enabled/disabled as a unit. In many cases, the other target-focused commands
(i.e. target_compile_definitions(), target_compile_options() and
target_include_directories()) can then also be used locally within the
subdirectory that they relate to. This helps keep information close to the
location where it is relevant rather than spreading it across directories.
Avoid using variables to build up lists of sources to be passed back up through
directory hierarchies and eventually used to create a target, define compiler
flags, etc. The use of variables instead of operating on targets directly is
much more fragile, more verbose and less likely to result in CMake catching
typos or other errors.
根据上述内容并重申 第 4 章“构建简单目标”中的建议之一,避免不必要地使用变量来保存目标或项目名称的常见做法。特别应该避免以下模式:
Following on from the above and reiterating one of the recommendations from Chapter 4, Building Simple Targets, avoid the all too common practice of unnecessarily using a variable to hold the name of a target or project. The following pattern in particular should be avoided:
set(projectName ...)
project(${projectName})
add_executable(${projectName} ...)set(projectName ...)
project(${projectName})
add_executable(${projectName} ...)
上面的例子将一些不应该如此密切相关的事物联系在一起。项目名称应该很少改变。直接在命令中指定项目名称project(),如果需要在项目的其他地方引用,则使用 CMake 提供的标准变量。对于目标,目标名称的使用如此广泛,以至于尝试在变量中携带它既麻烦又容易出错。为目标命名并在整个项目中一致使用该名称。即使整个项目中只有一个目标,它也不一定必须与项目名称相同,并且应将两者视为不同的。
The above example ties together things that should not be so strongly related.
The project name should rarely change. Specify the name of the project directly
in the project() command and use the standard variables CMake provides if it
needs to be referred to elsewhere in the project. For targets, the target name
is used so widely that trying to carry it around in a variable is both
cumbersome and error prone. Give the target a name and use that name
consistently throughout the project. Even if there is only one target in the
whole project, it doesn’t necessarily have to be the same as the project name
and the two should be considered distinct.
添加测试时,请考虑使测试代码与正在测试的代码保持接近。这有助于将逻辑相关的代码放在一起,并鼓励开发人员保持测试最新。分发到源目录层次结构其他部分的测试很容易被忘记。对于涉及多个领域的测试(例如集成测试),局部性原则不那么强,因此将这些更高级别的测试收集在一个公共位置可能是合适的。顶级tests子目录适用于此类情况。
When adding tests, consider keeping the test code close to the code being
tested. This helps keep logically related code together and encourages
developers to keep tests up to date. Tests that are distributed to other parts
of the source directory hierarchy can easily be forgotten. For tests that draw
on multiple areas such as integration tests, the locality principle is not as
strong, so collecting these higher level tests in a common place may be
appropriate. The top level tests subdirectory is intended for situations such
as this.
对于较大的项目,请考虑是否值得在 IDE 工具中组织项目的呈现方式。如果有很多目标,则除非使用目标属性添加某些结构,否则可能很难使用该项目FOLDER。对于那些具有多个源的目标,也可以使用该source_group()命令来组织它们,该命令可用于围绕任何有意义的概念或功能定义组层次结构。
For larger projects, consider whether it is worth organizing the way the
project is presented in IDE tools. If there are many targets, it
can be difficult to work with the project unless some structure is added
using the FOLDER target property. For those targets with many sources, they
too can be organized using the source_group() command, which
can be used to define group hierarchies around whatever concepts or features
make sense.
应特别考虑预计在 Windows 上构建的项目,尤其是开发人员可能使用 Visual Studio IDE 的项目。缺乏RPATH支持意味着可执行文件依赖于能够在同一目录中或通过PATH
环境变量找到其 DLL 依赖项。这会影响测试程序的运行ctest以及开发人员从 Visual Studio IDE 中运行可执行文件的能力。强制所有可执行文件和 DLL 放入同一输出目录是解决此问题的一种方法,通过各种…OUTPUT_DIRECTORY目标属性及其关联CMAKE_…OUTPUT_DIRECTORY变量使之成为可能。它们经常用于创建镜像安装项目时使用的目录布局。避免在构建后规则或自定义任务中复制 DLL 以将它们放在多个位置,以便其他可执行文件可以找到它们。这是脆弱的,很容易导致过时的 DLL 被错误地使用。
Special consideration should be given to projects that are anticipated to be
built on Windows, especially where developers may use the Visual Studio IDE.
The lack of RPATH support means executables rely on being able to
find their DLL dependencies in either the same directory or via the PATH
environment variable. This impacts both test programs run through ctest and
the developer’s ability to run executables from within the Visual Studio IDE.
Forcing all executables and DLLs into the same output directory is one solution
to this problem, made possible by the various …OUTPUT_DIRECTORY target
properties and their associated CMAKE_…OUTPUT_DIRECTORY variables. These
are frequently used to create a directory layout that mirrors that used when
the project is installed. Avoid copying DLLs in post build rules or custom
tasks to put them in multiple locations so that other executables can find
them. This is fragile and can easily result in stale DLLs mistakenly being
used.
理想情况下,测试程序不应与主程序和 DLL 收集到同一位置。某些测试代码可能需要查找与其自身位置相关的其他文件,因此甚至可能需要将它们分开。使用
ENVIRONMENTtest 属性来指定适当的属性,PATH以确保测试在运行时可以找到其 DLL ctest。还可以考虑使用 CMake 3.13 或更高版本,并VS_DEBUGGER_ENVIRONMENT在所有可执行目标上填充该属性,以便它们可以直接从 Visual Studio IDE 中运行。
Test programs would ideally not be collected to the same place as the main
programs and DLLs. Some test code may need to find other files relative to
their own location, so keeping them separate may even be a requirement. Use the
ENVIRONMENT test property to specify an appropriate PATH to ensure tests
can find their DLLs when run through ctest. Also consider using CMake 3.13 or
later and populating the VS_DEBUGGER_ENVIRONMENT property on all executable
targets too so that they can be run directly from within the Visual Studio IDE.
使用 Visual Studio 生成器时,最好将 PDB 设置保留为默认值。这通常会导致 PDB 文件出现在开发人员期望的位置,并且名称与其对应的可执行文件或库相匹配。当使用生成器表达式时,尝试更改 PDB 文件的输出目录会带来实现复杂性,并且在某些情况下很难将 PDB 文件放入所需的目录。
When using the Visual Studio generator, prefer to leave the PDB settings at their defaults. This typically results in the PDB file appearing in the location developers expect and with a name that matches the executable or library they correspond to. Trying to change the output directory of PDB files has implementation complexities when generator expressions are used and it can be difficult to get the PDB files into the desired directory in some cases.
如果项目的持续集成系统在每个作业之前没有清除 CMake 缓存(例如,它使用增量构建),则必须注意避免先前构建中的缓存变量以意外的方式影响后续构建。更喜欢使用该CMAKE_PROJECT_<PROJNAME>_INCLUDE变量通过源代码控制下的专用文件注入任何特定于 CI 的逻辑。这允许设置常规 CMake 变量,而无需将它们保存在缓存中,从而减少了如果这些变量随后从 CI 逻辑中删除,它们影响后续构建的机会。
If a project’s continuous integration system does not clear the CMake cache
before each job (e.g. it uses incremental builds), care must be taken to avoid
having cache variables from a previous build affect later builds in unintended
ways.
Prefer to use the CMAKE_PROJECT_<PROJNAME>_INCLUDE variable to inject any
CI-specific logic via a dedicated file under source control.
This allows regular CMake variables to be set without having to save them in
the cache, thereby reducing opportunities for them to affect later builds if
those variables are subsequently removed from the CI logic.
另一方面,如果每个 CI 构建都以空的初始构建目录开始,则预设可能是定义 CI 配置的更方便、更稳健的机制。项目应该只提供一个文件,并让开发人员根据需要CMakePresets.json创建自己的文件。CMakeUserPresets.json避免使用预设名称,因为CMakePresets.json这可能会与开发人员在自己的用户预设文件中使用的名称发生冲突。CMakePresets.json一个好的策略是每个预设名称都以前缀(ci-如项目名称)开头。对于开发人员,请考虑创建一个CMakeUserPresets.json文件来捕获可在不同项目中使用的通用配置,或者至少需要每个项目进行最少的定制。用于inherits以“混合”方式组合预设,以避免在相关预设中重复相同的细节。添加CMakeUserPresets.json到用户范围的源代码控制忽略列表中,以最大限度地减少该文件被提交到存储库的机会。
On the other hand, if every CI build starts with an empty initial build
directory, then presets may be a more convenient and more robust mechanism for
defining the CI configurations.
Projects should only provide a CMakePresets.json file and let developers
create their own CMakeUserPresets.json file if they want to.
Avoid using preset names in CMakePresets.json that would be likely to clash
with names developers would use in their own user presets file.
A good strategy is to start each preset name in CMakePresets.json with a
prefix like ci- or the project name.
For developers, consider creating a CMakeUserPresets.json file that captures
common configurations which can be used across different projects, or at least
that require minimal tailoring per project.
Use inherits to compose presets in a "mix-in" fashion to avoid repeating the
same details across related presets.
Add CMakeUserPresets.json to the user-wide source control ignore list to
minimize the chances of that file ever being committed to a repository.
对于较大的项目,构建时间可能非常重要,在极端情况下会延长至数小时。构建时间过长的影响不仅仅是烦恼,它还可能促使开发人员采取不良做法,并阻碍新想法的实验。长时间的构建还会给持续集成系统带来困难,导致作业延迟并增加对硬件资源的要求,以应对高构建量。
For larger projects, build times can be non-trivial, extending out to hours in extreme cases. The impact of a long build time can go beyond mere annoyance, it can drive developer behavior toward undesirable practices and discourage experimentation of new ideas. Long builds can also cause difficulties with continuous integration systems, causing job delays and increasing requirements on hardware resources to cope with the high build volumes.
有多种因素可能导致构建时间过长。不良的项目结构可能会导致相同的源文件使用相同的设置针对不同的目标进行多次编译。适当使用库通常可以消除此类问题,尽管这可能需要一些重构来解耦部分代码。
A variety of factors can contribute to long build times. Poor project structure can lead to the same source files being compiled multiple times with the same settings for different targets. Appropriate use of libraries can usually eliminate such problems, although this may require some refactoring to decouple parts of the code.
指定实际上不需要链接的目标之间的链接关系是导致效率低下的另一个原因。它降低了构建工具并行执行构建任务的能力,并且扩展了每当重建依赖项时都必须重新链接的目标集。
Specifying linking relationships between targets that don’t actually need to be linked is another source of inefficiency. It reduces the ability of the build tool to execute build tasks in parallel and it expands the set of targets that have to be relinked whenever the dependee is rebuilt.
在 C 或 C++ 项目的公共标头中公开私有详细信息是代码结构如何导致在进行更改时重建更多内容的另一个示例。减少代码之间的耦合是在开发过程中进行更改时减少增量构建时间的最有效方法之一。第 20.2 节“源代码访问版本详细信息”中介绍的材料是使用代码结构来最大限度地减少重建时间的一个主要示例。
Exposing private details in public headers of C or C++ projects is another example of how code structure can cause more things to be rebuilt than necessary when a change is made. Reducing coupling between code is one of the most effective ways to reduce incremental build times when making changes during development. The material covered in Section 20.2, “Source Code Access To Version Details” is a prime example of using code structure to minimize rebuild times.
在寻求提高构建性能时,解决上述结构问题应该是首要任务。指定实体之间的正确关系是许多其他技术稳健或有效的先决条件。解决了这些领域后,注意力可以转向本章其余部分讨论的技术。
When seeking to improve build performance, addressing structural problems such as those mentioned above should be the first priority. Specifying correct relationships between entities is a pre-requisite for many other techniques to be robust or effective. With those areas addressed, attention can turn to the techniques discussed in the remainder of this chapter.
统一构建(有时也称为巨型构建)是将通常单独编译的源文件有效地连接起来以生成少量的较大文件。如果每个单独的源文件都包含相似的标头集,则编译器通常会多次处理这些标头,每个单独的源文件处理一次。当各个源连接成单个组合源文件时,编译器可以避免多次处理每个标头。如果标头处理时间很重要,则可以显着节省构建时间。
A unity build (also sometimes referred to as a jumbo build) is where source files that would normally be compiled individually are effectively concatenated to produce a smaller number of larger files. If each of the individual source files include similar sets of headers, then the compiler would normally be processing those headers multiple times, one for each individual source file. When the individual sources are concatenated into a single combined source file, the compiler can avoid processing each header more than once. If the header processing time is non-trivial, this can lead to meaningful savings in build time.
CMake 3.16 及更高版本支持自动将目标源转换为统一构建。通过将UNITY_BUILDtarget 属性设置为 true,CMake 将负责将源组合成一个或多个统一源,并构建这些源而不是原始源。这个过程对项目来说是透明的,只需要通过目标属性启用该功能即可。并非所有语言都可以通过这种方式组合其源代码,但 CMake 至少支持对 C 和 C++ 源代码执行此操作(其他语言的源代码将保持原样并单独编译)。来源只会与同一语言的其他来源合并。
CMake 3.16 and later supports automatically converting a target’s sources to a
unity build.
By setting the UNITY_BUILD target property to true, CMake will take care
of combining sources into one or more unity sources and building those instead
of the originals.
This process is transparent to the project, requiring nothing more than
enabling the feature via the target property.
Not all languages can have their sources combined in this way, but CMake
supports doing so for at least C and C++ sources (other languages’ sources
will be left as they are and compiled individually).
Sources will only be combined with other sources of the same language.
创建目标时,目标属性由变量初始化UNITY_BUILD。
CMAKE_UNITY_BUILD将该变量设置为 true 可能是在整个项目中启用统一构建的有效方法,尽管它通常应该是一个缓存变量,以允许开发人员在需要时轻松关闭它。使其成为非缓存变量可能会导致分层构建出现问题,因为开发人员无法轻松禁用依赖子项目中的统一构建。
The UNITY_BUILD target property is initialized by the
CMAKE_UNITY_BUILD variable when the target is created.
Setting that variable to true can be an effective way to enable unity builds
across the entire project, although it should usually be a cache variable to
allow developers to easily switch it off if they need to.
Making it a non-cache variable could cause problems in hierarchical builds,
since the developer would not be able to easily disable unity builds in
dependency sub-projects.
在实践中,可能需要的不仅仅是简单地打开统一构建功能才能获得有价值的甚至可构建的结果。以这种方式组合源文件时需要注意一些限制:
In practice, it may take more than simply turning on the unity build feature to get a worthwhile or even buildable result. There are some restrictions to be aware of when combining source files in this way:
使用命名约定来避免重复文件范围实体的符号名称等策略可以帮助解决上述第一点。有时,这可能不切实际,或者可能存在其他限制阻止此类更改。对于第二点,应将大型源文件排除在统一构建之外,因为当它们已经很大时,几乎没有什么好处。
Strategies like using a naming convention to avoid duplicating symbol names of file scope entities can help with the first of the above points. Sometimes, that may not be practical or there may be other constraints preventing such changes. For the second point, large source files should be excluded from unity builds, since there is little to gain when they are already of a substantial size.
CMake 提供了一些控件,在上述情况下可能会有所帮助。如果少数来源有问题,可以根据具体情况排除它们与其他来源合并。这是通过将SKIP_UNITY_BUILD_INCLUSION有问题的源文件的属性设置为 true 来完成的。这些来源将永远不会与其他来源结合在一起构建统一版本。请注意,CMake 已排除任何通常不编译的源文件(例如头文件)或定义了以下任何源属性的任何源文件:
CMake provides a few controls which may help in situations like the above.
If a small number of sources are problematic, they can be excluded from being
combined with other sources on a case-by-case basis.
This is done by setting the SKIP_UNITY_BUILD_INCLUSION property on the
problematic source files to true.
Those sources will then never be combined with others in a unity build.
Note that CMake already excludes any source file which isn’t normally compiled
(e.g. header files) or any source file which has any of the following source
properties defined:
COMPILE_OPTIONS
COMPILE_OPTIONS
COMPILE_DEFINITIONS
COMPILE_DEFINITIONS
COMPILE_FLAGS
COMPILE_FLAGS
INCLUDE_DIRECTORIES
INCLUDE_DIRECTORIES
为了解决名称冲突,特别是对于 C++ 匿名命名空间中的符号,UNITY_BUILD_UNIQUE_IDCMake 3.20 或更高版本中提供的目标属性可能很有用。它的官方文档包含对其要解决的问题类型的描述、其使用细节和一个简单的示例。但总的来说,如果可能的话,更愿意重新编写源代码以避免名称冲突,因为它会更简单且更容易理解。
For resolving name clashes, especially for symbols in C++ anonymous
namespaces, the UNITY_BUILD_UNIQUE_ID target property available with
CMake 3.20 or later may be useful.
It’s official documentation contains a description of the types of problems it
is intended to solve, details of its usage and a simple example.
In general though, prefer to rework the source code to avoid the name clashes
if possible, as it will be simpler and much easier to understand.
对于 CMake 3.18 或更高版本,UNITY_BUILD_MODE目标属性指定选择将哪些源组合在一起的方法。如果已定义,此属性可以保存值BATCH或GROUP。如果未定义该属性或者使用 CMake 3.17 或更早版本,则行为与BATCH.
With CMake 3.18 or later, the UNITY_BUILD_MODE target property specifies
the method for choosing which sources to combine together.
If defined, this property may hold the values BATCH or GROUP.
If the property is not defined or if using CMake 3.17 or earlier, the behavior
is the same as BATCH.
在BATCH模式下,CMake 根据源添加到目标的顺序来组合源。目标UNITY_BUILD_BATCH_SIZE属性控制可以组合的最大源数。特定语言的第一个UNITY_BUILD_BATCH_SIZE文件将被组合到一个统一源中,下一个UNITY_BUILD_BATCH_SIZE源将被组合到另一个统一源中,依此类推,直到所有原始源都被包含或由于上述原因而被跳过。如果定义了该变量,则该属性由该变量初始化CMAKE_UNITY_BUILD_BATCH_SIZE,否则为其指定初始值 8。
In BATCH mode, CMake combines sources based on the order in which they were
added to the target.
The UNITY_BUILD_BATCH_SIZE target property controls the maximum number of
sources which may be combined.
The first UNITY_BUILD_BATCH_SIZE files for a particular language will be
combined into one unity source, the next UNITY_BUILD_BATCH_SIZE sources will
be combined into another unity source, and so on until all original sources
have been included or skipped for reasons discussed above.
This property is initialized by the CMAKE_UNITY_BUILD_BATCH_SIZE variable
if that variable is defined, otherwise it is given an initial value of 8.
当源都不是特别大时,此方法适用。如果某些源比其他源大得多,则可以将大源彼此分开列出,作为尽量减少它们组合在一起的可能性的粗略方法。这不是特别强大,但对于某些项目来说可能已经足够了。
This method is suitable when none of the sources are particularly big. If some sources are much larger than others, the large sources can be listed away from each other as a crude way of minimizing the likelihood that they will be combined together. This is not particularly robust, but may be good enough for some projects.
option(CMAKE_UNITY_BUILD "Enable unity builds")
add_executable(MyApp
someBigSource.cpp
little1.cpp
little1.h ①
little2.cpp
little2.h ①
# ---------
anotherBigSource.cpp
customFlags.cpp ②
mustBeSeparate.cpp ③
small1.cpp
# ---------
differentLang1.c
differentLang2.c
)
set_target_properties(MyApp PROPERTIES
UNITY_BUILD_BATCH_SIZE 3
)
set_source_files_properties(customFlags.cpp PROPERTIES
COMPILE_DEFINITIONS
COMPVER=${CMAKE_CXX_COMPILER_VERSION}
)
set_source_files_properties(mustBeSeparate.cpp PROPERTIES
SKIP_UNITY_BUILD_INCLUSION YES
)option(CMAKE_UNITY_BUILD "Enable unity builds")
add_executable(MyApp
someBigSource.cpp
little1.cpp
little1.h ①
little2.cpp
little2.h ①
# ---------
anotherBigSource.cpp
customFlags.cpp ②
mustBeSeparate.cpp ③
small1.cpp
# ---------
differentLang1.c
differentLang2.c
)
set_target_properties(MyApp PROPERTIES
UNITY_BUILD_BATCH_SIZE 3
)
set_source_files_properties(customFlags.cpp PROPERTIES
COMPILE_DEFINITIONS
COMPVER=${CMAKE_CXX_COMPILER_VERSION}
)
set_source_files_properties(mustBeSeparate.cpp PROPERTIES
SKIP_UNITY_BUILD_INCLUSION YES
)
SKIP_UNITY_BUILD_INCLUSIONsource 属性设置为 true,因此源文件明确从 Unity 源中排除。SKIP_UNITY_BUILD_INCLUSION source property set to true.someBigSource.cpp通过将统一批量大小限制为 3并确保在这两个大源anotherBigSource.cpp文件之间列出足够的源文件以使其在源列表中至少相距 3 个源,将两个大源文件保持分开。CMake 将把someBigSource.cpp,little1.cpp和合并little2.cpp到第一个统一源中,然后将anotherBigSource.cpp和 合并small1.cpp到第二个统一源中。另请注意,differentLang1.c和differentLang2.c不与其他 C++ 源代码组合,它们被放置在自己的统一源代码中,因为它们是不同的语言。统一源的实际名称是内部实现细节,项目不应尝试直接引用它们。
The two big source files someBigSource.cpp and anotherBigSource.cpp are
kept separate by limiting the unity batch size to 3 and ensuring that there are
enough source files listed between these two that they are at least 3 sources
apart in the source list.
CMake will combine someBigSource.cpp, little1.cpp and little2.cpp into
the first unity source, then anotherBigSource.cpp and small1.cpp into a
second unity source.
Note also that differentLang1.c and differentLang2.c are not combined with
the other C++ sources, they are placed in their own unity source because they
are a different language.
The actual names of the unity sources are an internal implementation detail,
projects should not attempt to refer to them directly.
当需要更精确地控制源的组合方式时,
GROUP模式更合适。当UNITY_BUILD_MODE设置为 时GROUP,目标中的每个源文件都需要使用UNITY_GROUP
源文件属性指定其所属的统一组的名称。任何未定义此属性的源都不会添加到统一源中,并将直接编译(即,就像未启用统一构建一样)。
When more precise control is needed over how sources should be combined,
GROUP mode is more appropriate.
When UNITY_BUILD_MODE is set to GROUP, each source file in the target needs
to specify the name of the unity group it belongs to with the UNITY_GROUP
source file property.
Any source that does not define this property will not be added to a unity
source and will be compiled directly (i.e. as though unity builds were not
enabled).
没有批量大小限制应用于统一组,该UNITY_BUILD_BATCH_SIZE
属性被忽略。该项目完全控制合并哪些来源。项目组的采购方式可能有不同的策略。它可以基于文件大小、包含的标头集、将相关代码放在一起以最大化优化机会等。下面显示了对上一个示例的源进行分组的另一种方法:
No batch size limits are applied to unity groups, the UNITY_BUILD_BATCH_SIZE
property is ignored.
The project is in full control over which sources are combined.
There could be different strategies for how the project groups sources.
It could be based on file size, on the set of headers that are included, on
keeping related code together to maximize optimization opportunities, etc.
The following shows an alternative way of grouping the sources of the previous
example:
option(CMAKE_UNITY_BUILD "Enable unity builds")
add_executable(MyApp
someBigSource.cpp
anotherBigSource.cpp
little1.cpp
little1.h
little2.cpp
little2.h
small1.cpp
customFlags.cpp
mustBeSeparate.cpp
differentLang1.c
differentLang2.c
)
set_source_files_properties(customFlags.cpp PROPERTIES
COMPILE_DEFINITIONS
COMPVER=${CMAKE_CXX_COMPILER_VERSION}
)
set_target_properties(MyApp PROPERTIES
UNITY_BUILD_MODE GROUP
)
set_source_files_properties(
little1.cpp little2.cpp small1.cpp
PROPERTIES UNITY_GROUP small
)
set_source_files_properties(
differentLang1.c differentLang2.c
PROPERTIES UNITY_GROUP different
)option(CMAKE_UNITY_BUILD "Enable unity builds")
add_executable(MyApp
someBigSource.cpp
anotherBigSource.cpp
little1.cpp
little1.h
little2.cpp
little2.h
small1.cpp
customFlags.cpp
mustBeSeparate.cpp
differentLang1.c
differentLang2.c
)
set_source_files_properties(customFlags.cpp PROPERTIES
COMPILE_DEFINITIONS
COMPVER=${CMAKE_CXX_COMPILER_VERSION}
)
set_target_properties(MyApp PROPERTIES
UNITY_BUILD_MODE GROUP
)
set_source_files_properties(
little1.cpp little2.cpp small1.cpp
PROPERTIES UNITY_GROUP small
)
set_source_files_properties(
differentLang1.c differentLang2.c
PROPERTIES UNITY_GROUP different
)
在上面,只有一些源文件定义了组。那些不这样做的将被单独编译,这在这种情况下很方便someBigSource.cpp,因为anotherBigSource.cpp和mustBeSeparate.cpp
都应该从统一构建中排除。源customFlags.cpp也被排除(它必须如此,因为它具有其
COMPILE_DEFINITIONS源属性集)。
In the above, only some of the source files have a group defined.
Those that don’t will be compiled individually, which is convenient in this
case since someBigSource.cpp, anotherBigSource.cpp and mustBeSeparate.cpp
should all be excluded from unity builds.
The customFlags.cpp source is also excluded (it has to be since it has its
COMPILE_DEFINITIONS source property set).
正如上面的例子所示,项目有时可以提供比BATCH方法更合适的分组。它带来的额外冗长和维护负担应该与构建性能的总体收益进行权衡。
As the above example shows, the project can sometimes provide more appropriate
grouping than the BATCH method.
The extra verbosity and maintenance burden it introduces should be weighed
against any overall gain in build performance.
有时可以缩短构建时间的另一种技术是使用预编译头来缓存编译器完成的一些工作。对于头文件处理对构建时间影响很大的情况,此技术可以节省大量时间。
Another technique that can sometimes improve build times is to employ precompiled headers to cache some of the work done by the compiler. For cases where header file processing is a non-trivial contributor to build times, this technique can lead to a worthwhile saving.
大多数主要编译器都支持预编译头,但各种实现之间存在显着差异。CMake 3.16 引入了对预编译头的直接支持,这消除了在每个项目中管理这些差异的负担。指示 CMake 为目标设置预编译头的主要方法是使用命令target_precompile_headers()。这与所有其他target_…()命令遵循相同的形式:
Most major compilers support precompiled headers, but there are significant
differences between the various implementations.
CMake 3.16 introduced direct support for precompiled headers which takes away
the burden of having to manage these differences in each project.
The primary way to instruct CMake to set up precompiled headers for a target is
with the target_precompile_headers() command.
This follows the same form as all the other target_…() commands:
target_precompile_headers(targetName
<PRIVATE|PUBLIC|INTERFACE> headers...
[<PRIVATE|PUBLIC|INTERFACE> headers...] ...
)target_precompile_headers(targetName
<PRIVATE|PUBLIC|INTERFACE> headers...
[<PRIVATE|PUBLIC|INTERFACE> headers...] ...
)
,PRIVATE和关键字具有其通常的含义,指定关键字后面的 是否PUBLIC应应用于指定的目标和/或链接到目标的事物。
标头附加到名为 的目标属性
,而标头则附加到
属性。
标头附加到这两个属性。CMake 使用这些属性构建一组标头,在编译指定目标的源或链接到它的内容时,将强制包含这些标头(视情况而定)。用于完成此操作的方法因编译器而异,但从项目角度来看,重要的一点是不需要更改源代码。CMake 将生成文件并使用命令行参数使编译器包含指定的标头,而不是要求修改源以显式包含任何特殊标头。INTERFACEheadersPRIVATEPRECOMPILE_HEADERSINTERFACEINTERFACE_PRECOMPILE_HEADERSPUBLIC
The PRIVATE, PUBLIC and INTERFACE keywords have their usual meaning,
specifying whether the headers that follow the keyword should be applied to
the specified target and/or to things linking against the target.
PRIVATE headers are appended to a target property called
PRECOMPILE_HEADERS, whereas INTERFACE headers are appended to the
INTERFACE_PRECOMPILE_HEADERS property.
PUBLIC headers are appended to both properties.
CMake uses these properties to build up a set of headers that will be
force-included when compiling sources for the specified target or things that
link against it, as appropriate.
The method used to accomplish this varies between compilers, but from a project
perspective, an important point is that no source changes are required.
CMake will generate files and use command line arguments to make compilers
include the specified headers rather than requiring sources to be modified to
explicitly include any special header.
在典型场景中,预编译头应指定为PRIVATE. 支持预编译头的非私有关系的主要原因是支持接口库,其存在的原因是将常用的编译器定义、选项等收集在一起。目标选择通过链接到接口库将这些应用到自己身上。以下示例演示了这种安排:
In typical scenarios, precompile headers should be specified as PRIVATE.
The main reason for supporting non-private relationships for a precompile
header is to support interface libraries whose reason for existence is to
collect together commonly used compiler definitions, options and so forth.
Targets opt in to having these applied to themselves by linking to the
interface library.
The following example demonstrates such an arrangement:
add_library(MyCommonPCH INTERFACE)
target_precompile_headers(MyCommonPCH INTERFACE
<iostream>
<vector>
<algorithm>
[["myAlgo.h"]]
)
target_include_directories(MyCommonPCH INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
add_library(ShipBuilder ships.cpp)
add_library(CarBuilder cars.cpp)
target_link_libraries(ShipBuilder PRIVATE MyCommonPCH)
target_link_libraries(CarBuilder PRIVATE MyCommonPCH)add_library(MyCommonPCH INTERFACE)
target_precompile_headers(MyCommonPCH INTERFACE
<iostream>
<vector>
<algorithm>
[["myAlgo.h"]]
)
target_include_directories(MyCommonPCH INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
add_library(ShipBuilder ships.cpp)
add_library(CarBuilder cars.cpp)
target_link_libraries(ShipBuilder PRIVATE MyCommonPCH)
target_link_libraries(CarBuilder PRIVATE MyCommonPCH)
在提供给 的标头列表中target_precompile_headers(),请注意如何使用 lua 样式的括号来确保引号包含在标头中。每个标头名称周围的分隔符都很重要,因为它们会影响当 CMake 形成用于收集要预编译的标头的内部文件时如何处理该标头。如果标题被尖括号或引号包围,则它们按原样使用。其效果就好像它们是在它们之前#include并逐字使用的。此类标头应在使用它们的源文件的标头搜索路径中找到。这就是为什么该示例还添加CMAKE_CURRENT_LIST_DIR了链接对象的标头搜索路径MyCommonPCH(请参阅第 26.2.1 节“接口属性”,
了解$<BUILD_INTERFACE:…>此处也使用生成器表达式的原因)。
In the list of headers provided to target_precompile_headers(), note how
lua-style brackets have been used to ensure that the quotes are included as
part of the header.
The delimiters around each header name are significant, as they affect how that
header is treated when CMake forms the internal files used to collect the
headers to be precompiled.
Where a header is surrounded by angle brackets or quotes, they are used as is.
The effect is as though they were preceded by #include and used verbatim.
Such headers are expected to be found on the header search path for the source
file that is using them.
This is why the example also adds CMAKE_CURRENT_LIST_DIR to the header search
path for things that link against MyCommonPCH (see Section 26.2.1, “Interface Properties”
for why the $<BUILD_INTERFACE:…> generator expression is also used here).
如果根本没有尖括号或引号分隔符,该
target_precompile_headers()命令将假定标头是相对于当前源目录的路径,并且如果它们还不是绝对路径,则将它们转换为绝对路径。这将使此类接口库不适合安装,因为它们的接口属性中嵌入了来自构建树的路径。
Where there are no angle bracket or quote delimiters at all, the
target_precompile_headers() command will assume headers are paths relative to
the current source directory and will convert them to an absolute path if they
are not already absolute.
This would make such interface libraries unsuitable for being installed, since
they would have paths from the build tree embedded in their interface
properties.
定义一组通用预编译头的一种可能更简单、更有效的方法是使用命令的替代形式
target_precompile_headers():
A potentially simpler and more efficient way to define a common set of
precompile headers is to use an alternative form of the
target_precompile_headers() command:
target_precompile_headers(targetName
REUSE_FROM otherTarget
)target_precompile_headers(targetName
REUSE_FROM otherTarget
)
该REUSE_FROM关键字指示命令也应用从otherTarget到 的预编译头。targetName请注意,这并不是将预编译头设置复制到targetName,它实际上是重用 中的预编译头配置otherTarget,包括为其生成的内部文件。与使用界面库在目标之间共享通用设置相比,这可能会导致生成的文件更少,从而可能导致使用的磁盘空间更少。在具有多个目标共享相同预编译头的项目中,节省的成本可能会很大。
The REUSE_FROM keyword instructs the command to apply the precompile headers
from otherTarget to targetName as well.
Note that this is not copying the precompile header settings to targetName,
it is literally reusing the precompile header configuration from otherTarget,
including the internal files generated for it.
This can lead to fewer generated files compared to using an interface library
to share common settings between targets, potentially resulting in lower disk
space being used.
In projects with many targets sharing the same precompiled header, the savings
can be significant.
该形式的一个重要先决条件REUSE_FROM是两个目标必须使用一组兼容的编译器标志、定义等。如果没有此限制,预编译标头可能与使用其自己的独特设置otherTarget生成的预编译标头不同。targetName不影响预编译头的编译器标志或定义可能在两个目标之间有所不同,或者在一个目标中被省略,而在另一个目标中则不然。项目应致力于尽量减少此类情况,以避免潜在的编译器警告。
An important prerequisite for the REUSE_FROM form is that both targets must
use a compatible set of compiler flags, definitions, etc.
Without this restriction, the precompiled header from otherTarget might be
different to what should have been generated for targetName with its own
unique settings.
Compiler flags or definitions which do not affect the precompiled header can
potentially be different between the two targets, or omitted in one but not the
other.
Projects should aim to minimize such cases to avoid potential compiler warnings.
在许多情况下,通过使用编译器缓存可以显着提高构建性能。这种缓存的目的是保留先前构建的目标文件,以便如果编译器被要求使用相同的标志、标头和工具链集编译完全相同的源文件,则可以直接从缓存中检索目标文件,而不是直接从缓存中检索目标文件。必须重新编译。配置良好的编译器缓存可能是减少构建时间的最有效方法之一。
In many cases, significant improvements in build performance can be achieved by employing a compiler cache. The purpose of such a cache is to keep previously built object files so that if the compiler is asked to compile the exact same source file with the same set of flags, headers and toolchain, the object file can be retrieved directly from the cache instead of having to be compiled again. A well-configured compiler cache can be one of the most effective ways to reduce build times.
使用 GCC 时,Clang 或任何声称与其中任何一个兼容的编译器ccache是最常用的编译器缓存工具之一。它以两种方式之一工作:
When using GCC, Clang or any compiler that claims to be compatible with either
of those, ccache is one of the most commonly used compiler cache tools.
It works in one of two ways:
ccache.
ccache.
第一个选项提供编译器缓存作为默认的系统范围行为,但它有缺点。主要限制是它不允许开发人员选择使用哪个编译器,而是锁定使用默认系统编译器的选择。在许多情况下,默认编译器相当旧,因此开发人员经常希望使用安装在其他位置的较新编译器。ccache另一个限制是,如果系统默认情况下不以管理员权限安装,则需要管理员访问权限才能设置。
The first option provides compiler caching as the default system-wide behavior,
but it has drawbacks.
The main limitation is that it doesn’t allow the developer to choose which
compiler to use, instead locking in the choice of using the default system
compiler.
In many cases, the default compiler is quite old, so developers frequently want
to use newer compilers installed in other locations.
Another limitation is that it requires administrator access to set up if the
system doesn’t install ccache that way by default.
一种更灵活的方法是在通常的编译器命令行之前添加ccache. 此方法允许使用任何支持的编译器,而不仅仅是系统默认的编译器。不需要进行系统范围的更改,因此任何用户都可以自由使用该技术。
A much more flexible approach is to precede the usual compiler command line
with ccache.
This method allows any supported compiler to be used, not just the system
default.
No system-wide change is required, so the technique can be employed freely by
any user.
一些 CMake 生成器支持编译器启动器功能,该功能与第二种使用ccache. 从 CMake 3.4 开始,<LANG>_COMPILER_LAUNCHER目标属性可用于以特定于语言的方式指定位于编译器命令行之前的项目列表。仅 Ninja 和 Makefile 生成器支持此功能。这些属性的默认值是
CMAKE_<LANG>_COMPILER_LAUNCHER在创建目标时从相应的变量中获取的。
Some CMake generators support a compiler launcher feature which maps very
closely to this second way of using ccache.
Starting with CMake 3.4, the <LANG>_COMPILER_LAUNCHER target properties
can be used to specify a list of items to precede the compiler command line in
a language-specific way.
This is supported by the Ninja and Makefiles generators only.
The default value for these properties are taken from the corresponding
CMAKE_<LANG>_COMPILER_LAUNCHER variables when a target is created.
下面演示了如何启用ccache整个构建,但前提是首先确认ccache可用。这确保了构建无论有没有ccache.
The following demonstrates how to enable ccache for the whole build, but only
after first confirming that ccache is available.
This ensures the build will work with or without ccache.
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()
对于大多数项目来说,上面的例子过于简单。ccache通常应设置某些配置选项以确保安全且高效的缓存行为。要在每个项目的基础上设置这些选项,CCACHE_…可以使用各种环境变量。文档中详细介绍了支持的变量ccache,但一些更重要的变量是:
For most projects, the above example is too simplistic.
Certain ccache configuration options should typically be set to ensure safe
and efficient caching behavior.
To set these options on a per-project basis, various CCACHE_… environment
variables can be used.
The supported variables are all detailed in the ccache documentation, but a
few of the more important ones are:
CCACHE_CPP2
CCACHE_CPP2
true以避免某些编译器出现虚假警告。这是 3.3 或更高版本的默认设置ccache,但为了也支持早期
ccache版本,项目应明确设置它。
true to avoid spurious warnings with some
compilers.
This is the default with ccache 3.3 or later, but to support earlier
ccache releases as well, the project should explicitly set it.
CCACHE_BASEDIR
CCACHE_BASEDIR
CCACHE_BASEDIR为${CMAKE_BINARY_DIR}可能会导致更多的缓存命中。对于用于将其依赖项引入构建的项目来说可能就是这种情况FetchContent,因为默认情况下它们总是将相同的依赖项放置在构建目录下的相同相对位置。相反,如果大多数来源往往来自项目本身,那么设置CCACHE_BASEDIR为${CMAKE_SOURCE_DIR}可能是更好的设置。开发人员应在项目中使用任一设置之前测试其有效性。
CCACHE_BASEDIR to ${CMAKE_BINARY_DIR} may result in
more cache hits.
This is likely to be the case for projects that use FetchContent to bring
their dependencies into the build, since they will always place the same
dependency at the same relative location under the build directory by default.
Conversely, if most of the sources tend to be from the project itself, then
setting CCACHE_BASEDIR to ${CMAKE_SOURCE_DIR} may be a better setting.
Developers should test the effectiveness of either setting before using it in
a project.
CCACHE_SLOPPINESS
CCACHE_SLOPPINESS
pch_defines,time_macros。文档的预编译头部分ccache解释了为什么需要这些设置。为了提高缓存命中性能,可能还需要添加
include_file_mtime到草率选项列表中。这理论上存在竞争条件的风险,但对于典型场景,这种竞争条件的可能性极小(预处理器包含的文件必须在编译源代码时更新)。ccache在决定是否添加
include_file_mtime草率选项之前,请参阅手册了解详细信息。
pch_defines,time_macros.
The Precompiled headers section of the ccache documentation explains why
these settings are needed.
For improved cache hit performance, it may also be desirable to add
include_file_mtime to the list of sloppiness options.
This comes with a theoretical risk of a race condition, but for typical
scenarios, that race condition is highly unlikely (files included by the
preprocessor would have to be updated while the source is being compiled).
Consult the ccache manual for details before deciding whether to add
include_file_mtime to the sloppiness options.
CCACHE_PREFIX
CCACHE_PREFIX
ccache支持与另一个编译器命令包装器结合使用。流行的示例包括distcc、icecc和 等工具sccache-dist
,它们将编译分布在多台机器上以减少构建时间。当链接多个编译器包装器时,建议调用ccache第一个包装器,然后通过环境变量指定另一个包装器
CCACHE_PREFIX(例如CCACHE_PREFIX=distcc)。
ccache如果编译命令的结果尚未在其缓存中,则处理到第二个包装器的链接。
ccache supports being used in conjunction with another
compiler command wrapper.
Popular examples include tools such as distcc, icecc and sccache-dist
which distribute compilation across multiple machines to reduce build times.
When chaining multiple compiler wrappers, it is recommended to have ccache as
the first wrapper invoked and then specify the other wrapper through the
CCACHE_PREFIX environment variable (e.g. CCACHE_PREFIX=distcc).
ccache then handles chaining to the second wrapper if the result of the
compile command is not already in its cache.
开发人员还应该熟悉所ccache提供的不同操作模式。直接模式、预处理器模式和依赖模式都有各自的优点和限制,其中直接模式是默认模式。请特别注意与预编译头等功能的交互,因为有时会出现问题。作为一个示例,在将预编译头与 Clang 一起使用时,建议使用依赖模式,以避免切换分支时出现时间戳问题,但在依赖模式下可能无法正确处理更改编译器定义。
Developers should also become familiar with the different modes of operation
that ccache offers.
Direct mode, preprocessor mode and depend mode all have their advantages and
restrictions, with direct mode being the default.
Pay special attention to the interaction with features like precompiled headers,
as problems can sometimes occur.
As one example, depend mode is advisable when using precompiled headers with
Clang to avoid issues with timestamps when switching branches, but changing
compiler definitions may not be handled correctly in depend mode.
这些<LANG>_COMPILER_LAUNCHER属性接受一个列表,因此可以使用 CMake 的命令模式在调用时设置相关的环境变量
ccache。例如:
The <LANG>_COMPILER_LAUNCHER properties accept a list, so CMake’s command
mode can be used to set the relevant environment variables when invoking
ccache.
For example:
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(ccacheEnv
CCACHE_CPP2=true
CCACHE_BASEDIR=${CMAKE_BINARY_DIR}
CCACHE_SLOPPINESS=pch_defines,time_macros
)
foreach(lang IN ITEMS C CXX OBJC OBJCXX CUDA) ①
set(CMAKE_${lang}_COMPILER_LAUNCHER
${CMAKE_COMMAND} -E env
${ccacheEnv} ${CCACHE_PROGRAM}
PARENT_SCOPE
)
endforeach()
endif()find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(ccacheEnv
CCACHE_CPP2=true
CCACHE_BASEDIR=${CMAKE_BINARY_DIR}
CCACHE_SLOPPINESS=pch_defines,time_macros
)
foreach(lang IN ITEMS C CXX OBJC OBJCXX CUDA) ①
set(CMAKE_${lang}_COMPILER_LAUNCHER
${CMAKE_COMMAND} -E env
${ccacheEnv} ${CCACHE_PROGRAM}
PARENT_SCOPE
)
endforeach()
endif()
ccache更新的版本)。ccacheCUDAccache for all supported languages (a more recent ccache version
is required for CUDA).上述技术适用于 Ninja 或 Makefiles 生成器,但不适用于 Xcode 生成器。
ccache确实支持AppleClang编译器,但是必须使用不同的方法在编译器命令之前插入ccache并设置相关的环境变量。
The above technique is suitable for the Ninja or Makefiles generators, but not
for the Xcode generator.
ccache does support the AppleClang compiler, but a different method must be
used to insert ccache before the compiler command and set the relevant
environment variables.
CMAKE_XCODE_ATTRIBUTE_CC使用 Xcode 生成器时,可以通过设置和
变量来覆盖用于 C 和 C++ 的编译器CMAKE_XCODE_ATTRIBUTE_CXX。链接器还必须使用CMAKE_XCODE_ATTRIBUTE_LD和
CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS变量进行设置,以解决至少某些版本的 Xcode IDE 中的错误,如果仅设置CMAKE_XCODE_ATTRIBUTE_CC和,它可能会选择错误的链接器语言。CMAKE_XCODE_ATTRIBUTE_CXX奇怪的是,该错误仅限于 IDE 中的构建,任何使用命令行的构建都xcodebuild不会受到影响。另请记住,如第 23.1 节“CMake 生成器选择”中所述,
CMAKE_XCODE_ATTRIBUTE_…变量仅在顶层CMakeLists.txt文件中设置时才有效。
When using the Xcode generator, the compilers to use for C and C++ can be
overridden by setting the CMAKE_XCODE_ATTRIBUTE_CC and
CMAKE_XCODE_ATTRIBUTE_CXX variables.
The linkers must also be set with the CMAKE_XCODE_ATTRIBUTE_LD and
CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS variables to work around a bug in at least
some versions of the Xcode IDE where it can select the wrong linker language if
only CMAKE_XCODE_ATTRIBUTE_CC and CMAKE_XCODE_ATTRIBUTE_CXX are set.
Curiously, the bug is restricted to building within the IDE, any command-line
builds using xcodebuild are not affected.
Also recall that, as mentioned back in Section 23.1, “CMake Generator Selection”, the
CMAKE_XCODE_ATTRIBUTE_… variables only have an effect if set in the top
level CMakeLists.txt file.
每个 Xcode 项目变量只允许指定单个值,但命令行需要有多个选项(至少是ccache
可执行文件和要调用的真实编译器)。因此,需要编写一个单独的启动脚本,并将项目变量指向它们。还可以在这些启动脚本中设置相关的环境变量。例如:
Each of the Xcode project variables allow specifying only a single value, but
the command line needs to have multiple options (at least the ccache
executable and the real compiler to be invoked).
Therefore, a separate launch script needs to be written out and the project
variables pointed at them.
Relevant environment variables can also be set in these launch scripts.
For example:
# Write out launch scripts for C and C++ languages
foreach(lang IN ITEMS C CXX)
set(launch${lang} ${CMAKE_BINARY_DIR}/launch-${lang})
file(WRITE ${launch${lang}}
"#!/bin/bash\n\n"
"export CCACHE_CPP2=true\n"
"export CCACHE_BASEDIR=${CMAKE_BINARY_DIR}\n"
"export CCACHE_SLOPPINESS=pch_defines,time_macros\n"
"exec \"${CCACHE_PROGRAM}\" "
"\"${CMAKE_${lang}_COMPILER}\" \"$@\"\n"
)
execute_process(COMMAND chmod a+rx ${launch${lang}})
endforeach()
# Redirect Xcode to use our launchers
set(CMAKE_XCODE_ATTRIBUTE_CC ${launchC})
set(CMAKE_XCODE_ATTRIBUTE_CXX ${launchCXX})
set(CMAKE_XCODE_ATTRIBUTE_LD ${launchC})
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${launchCXX})# Write out launch scripts for C and C++ languages
foreach(lang IN ITEMS C CXX)
set(launch${lang} ${CMAKE_BINARY_DIR}/launch-${lang})
file(WRITE ${launch${lang}}
"#!/bin/bash\n\n"
"export CCACHE_CPP2=true\n"
"export CCACHE_BASEDIR=${CMAKE_BINARY_DIR}\n"
"export CCACHE_SLOPPINESS=pch_defines,time_macros\n"
"exec \"${CCACHE_PROGRAM}\" "
"\"${CMAKE_${lang}_COMPILER}\" \"$@\"\n"
)
execute_process(COMMAND chmod a+rx ${launch${lang}})
endforeach()
# Redirect Xcode to use our launchers
set(CMAKE_XCODE_ATTRIBUTE_CC ${launchC})
set(CMAKE_XCODE_ATTRIBUTE_CXX ${launchCXX})
set(CMAKE_XCODE_ATTRIBUTE_LD ${launchC})
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${launchCXX})
项目可以结合这两种方法来为ccache不同的生成器提供支持。在其自己的函数中实现逻辑并从顶级CMakeLists.txt文件中调用它(通常在project()
命令之后)可能会很有用。如果不是从顶层调用,该函数应该不执行任何操作,因为这是 Xcode 生成器的要求。它还确保顶层项目控制分层构建。该函数可以在自己的文件中实现,也可能在自己的源存储库中实现,然后通过FetchContent. 这是在多个项目之间共享相同逻辑的便捷方法。
Projects can combine the two methods to provide support for ccache across the
different generators.
It may be useful to implement the logic in its own function and call it from
the top level CMakeLists.txt file, typically just after the project()
command.
The function should do nothing if not called from the top level, since that is
a requirement for the Xcode generator.
It also ensures that the top level project is in control for hierarchical
builds.
The function can be implemented in its own file and potentially in its own
source repository, then added to the build via FetchContent.
This is a convenient way to share the same logic across many projects.
function(useCompilerCache)
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL
CMAKE_SOURCE_DIR)
return()
endif()
find_program(CCACHE_PROGRAM ccache)
if(NOT CCACHE_PROGRAM)
return()
endif()
set(ccacheEnv
CCACHE_BASEDIR=${CMAKE_BINARY_DIR}
CCACHE_CPP2=true
CCACHE_SLOPPINESS=pch_defines,time_macros
)
if(CMAKE_GENERATOR MATCHES "Ninja|Makefiles")
foreach(lang IN ITEMS C CXX OBJC OBJCXX CUDA)
set(CMAKE_${lang}_COMPILER_LAUNCHER
${CMAKE_COMMAND} -E env
${ccacheEnv} ${CCACHE_PROGRAM}
PARENT_SCOPE
)
endforeach()
elseif(CMAKE_GENERATOR STREQUAL Xcode)
foreach(lang IN ITEMS C CXX)
set(launch${lang}
${CMAKE_BINARY_DIR}/launch-${lang})
file(WRITE ${launch${lang}}
"#!/bin/bash\n\n")
foreach(keyVal IN LISTS ccacheEnv)
file(APPEND ${launch${lang}}
"export ${keyVal}\n")
endforeach()
file(APPEND ${launch${lang}}
"exec \"${CCACHE_PROGRAM}\" "
"\"${CMAKE_${lang}_COMPILER}\" \"$@\"\n")
execute_process(COMMAND
chmod a+rx ${launch${lang}})
endforeach()
set(CMAKE_XCODE_ATTRIBUTE_CC ${launchC}
PARENT_SCOPE)
set(CMAKE_XCODE_ATTRIBUTE_CXX ${launchCXX}
PARENT_SCOPE)
set(CMAKE_XCODE_ATTRIBUTE_LD ${launchC}
PARENT_SCOPE)
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${launchCXX}
PARENT_SCOPE)
endif()
endfunction()function(useCompilerCache)
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL
CMAKE_SOURCE_DIR)
return()
endif()
find_program(CCACHE_PROGRAM ccache)
if(NOT CCACHE_PROGRAM)
return()
endif()
set(ccacheEnv
CCACHE_BASEDIR=${CMAKE_BINARY_DIR}
CCACHE_CPP2=true
CCACHE_SLOPPINESS=pch_defines,time_macros
)
if(CMAKE_GENERATOR MATCHES "Ninja|Makefiles")
foreach(lang IN ITEMS C CXX OBJC OBJCXX CUDA)
set(CMAKE_${lang}_COMPILER_LAUNCHER
${CMAKE_COMMAND} -E env
${ccacheEnv} ${CCACHE_PROGRAM}
PARENT_SCOPE
)
endforeach()
elseif(CMAKE_GENERATOR STREQUAL Xcode)
foreach(lang IN ITEMS C CXX)
set(launch${lang}
${CMAKE_BINARY_DIR}/launch-${lang})
file(WRITE ${launch${lang}}
"#!/bin/bash\n\n")
foreach(keyVal IN LISTS ccacheEnv)
file(APPEND ${launch${lang}}
"export ${keyVal}\n")
endforeach()
file(APPEND ${launch${lang}}
"exec \"${CCACHE_PROGRAM}\" "
"\"${CMAKE_${lang}_COMPILER}\" \"$@\"\n")
execute_process(COMMAND
chmod a+rx ${launch${lang}})
endforeach()
set(CMAKE_XCODE_ATTRIBUTE_CC ${launchC}
PARENT_SCOPE)
set(CMAKE_XCODE_ATTRIBUTE_CXX ${launchCXX}
PARENT_SCOPE)
set(CMAKE_XCODE_ATTRIBUTE_LD ${launchC}
PARENT_SCOPE)
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${launchCXX}
PARENT_SCOPE)
endif()
endfunction()
典型的开发人员工作流程是进行代码更改,执行增量构建以重建依赖于更改的任何内容,然后重新运行一个或多个可执行文件、测试等。其中一些可能在调试器下运行,其中涉及加载可执行文件及其使用的任何共享库的相关调试符号。对于许多常见的工具链,默认设置会导致此工作流程效率低下。
A typical developer workflow is to make a code change, perform an incremental build to rebuild anything that depends on the change and re-run one or more executables, tests, etc. Some of these may be run under a debugger, which involves loading the relevant debug symbols for the executable and any shared libraries used by it. With many common toolchains, the default settings lead to a number of inefficiencies in this workflow.
编译器的一种常见默认行为是直接将调试信息存储在生成的目标文件中。在链接步骤中,链接器会处理所有这些调试信息,即使实际上不需要生成工作二进制文件。创建可执行文件或共享库时,链接器还可能嵌入相关调试信息的副本,这需要链接器处理目标文件中的所有符号以找出要存储的内容,包括处理同一符号的多个定义。这种嵌入还会导致调试信息的多个副本存储在构建中的各个对象文件和二进制文件中。此信息的大小通常非常重要,通常占目标文件和二进制文件大小的大部分。所有这些额外的处理和重复都会对构建性能产生显着影响。它还可能导致链接期间内存使用量非常高。
One common default behavior is for compilers to store debug information directly in the generated object files. At the link step, the linker then processes all this debug information, even though it isn’t actually needed to produce a working binary. When creating an executable or shared library, the linker may also embed a copy of the relevant debug information, which requires the linker to process all the symbols across the object files to work out what to store, including handling multiple definitions of the same symbol. This embedding also results in multiple copies of debug information being stored across the various object files and binaries in the build. The size of this information is frequently very significant, often making up the majority of the size of object files and binaries. All this extra processing and duplication can have a noticeable impact on the build performance. It can also lead to very high memory use during linking.
上述问题的一种解决方案是将调试信息存储在单独的文件中,而不是直接存储在目标文件、共享库或可执行文件中。任何相当新的 GCC 或 Clang 版本都支持名为split dwarf 的功能
,该功能正是这样做的,但默认情况下不启用它。可以通过添加编译器选项来打开它-gsplit-dwarf。然后,编译器将.dwo在目标文件旁边创建一个文件,然后在该目标文件中记录对 的引用,.dwo而不是完整的调试信息。此外,链接到该目标文件的任何可执行文件或共享库也将包含对其文件的引用,.dwo而不是嵌入调试信息的副本。
One solution to the above is to store the debug information in a separate file
instead of directly in the object file, shared library or executable.
Any reasonably recent version of GCC or Clang supports a feature called
split dwarf which does exactly this, but it is not enabled by default.
It can be turned on by adding the -gsplit-dwarf compiler option.
The compiler will then create a .dwo file beside the object file, then record
a reference to the .dwo in that object file instead of the full debug
information.
Furthermore, any executable or shared library that links in that object file
will also contain a reference to its .dwo file instead of embedding a copy of
the debug information.
为了从 split dwarf 中获得最大的好处,应该在整个构建中启用它。这确保了所有目标的所有调试信息都将与对象和二进制文件分开。开发人员可以手动将其添加到 CMake 缓存或将其设置为工具链文件的一部分。这是通过附加-gsplit-dwarf到像
CMAKE_C_FLAGS_DEBUG和 之类的变量CMAKE_CXX_FLAGS_DEBUG,或者…_INIT
在工具链文件的情况下附加到它们的对应变量来完成的。这不需要对项目进行任何更改,并且使开发人员可以完全自行决定是否使用该功能。
To gain the most benefit from split dwarf, it should be enabled for the whole
build.
This ensures that all debug information for all targets will be separated from
the object and binary files.
Developers can add it manually to the CMake cache or set it as part of a
toolchain file.
This is done by appending -gsplit-dwarf to variables like
CMAKE_C_FLAGS_DEBUG and CMAKE_CXX_FLAGS_DEBUG, or their …_INIT
counterparts in the case of toolchain files.
This requires no changes to the project and gives the developer full discretion
over whether or not to use the feature.
在某些情况下,项目本身可能需要添加该标志,例如在项目是在受控设置中构建的公司环境中。执行此操作时,请考虑第 15 章“编译器和链接器要点”中的建议,并且更喜欢使用add_compile_options()命令来设置相关目录属性而不是修改CMAKE_<LANG>_FLAGS_DEBUG变量。以下示例演示了如何完成此操作,包括测试是否支持该标志:
In some cases, it may be desirable for the project itself to add the flag, such
as in a company environment where the project is built in a controlled setup.
When doing this, consider the advice in Chapter 15, Compiler And Linker Essentials and
prefer to use the add_compile_options() command to set the relevant directory
properties rather than modifying the CMAKE_<LANG>_FLAGS_DEBUG variables.
The following example demonstrates how this can be done, including a test for
whether the flag is supported or not:
include(CheckCCompilerFlag)
check_c_compiler_flag("-gsplit-dwarf" HAVE_SPLIT_DWARF)
if(HAVE_SPLIT_DWARF)
# Only add for debug builds, but could also expand the
# generator expression to add for RelWithDebInfo too
add_compile_options("$<$<CONFIG:Debug>:-gsplit-dwarf>")
endif()include(CheckCCompilerFlag)
check_c_compiler_flag("-gsplit-dwarf" HAVE_SPLIT_DWARF)
if(HAVE_SPLIT_DWARF)
# Only add for debug builds, but could also expand the
# generator expression to add for RelWithDebInfo too
add_compile_options("$<$<CONFIG:Debug>:-gsplit-dwarf>")
endif()
-gSplit dwarf 在与、
-g1等选项的交互方面有一些微妙之处。这些标志的交互在过去曾引起过一些问题,因此如果添加此编译器标志(至少3.7) ccache,建议使用最新版本。同样,将调试信息拆分为单独的文件也可能会干扰分布式编译工具,例如. 开发人员应检查他们正在使用的工具版本的文档,并确保工具在启用 split dwarf 之前支持它。ccacheccachedistcc
Split dwarf has some subtleties around its interaction with options like -g,
-g1 and so on.
The interaction of these flags with ccache in particular has caused some
issues in the past, so it is advisable to use a recent ccache release if
adding this compiler flag (at least ccache 3.7).
Similarly, having the debug information split out to a separate file may also
interfere with distributed compilation tools like distcc.
Developers should check the documentation of the version of the tools they are
using and ensure the tools support split dwarf before enabling it.
如上一节所述,在使用调试版本时,由于调试符号处理,链接时间可能会很长。许多 Unix 系统上的默认 BFD 链接器在这方面表现不佳,但替代链接器可以做得更好。黄金链接器相对成熟,通常速度更快,并且使用的内存更少。它通常是可靠的,但在某些项目上可能会失败(例如,由于链接到仅适用于默认链接器的共享库)。llvm 链接器 ( lld) 可能会提供更好的性能,但它并不成熟,并且可能无法在所有情况下工作。
As discussed in the previous section, when working with debug builds, the link
time can be significant due to the debug symbol handling.
The default BFD linker on many Unix systems does not perform well in this
regard, but alternative linkers can do a much better job.
The gold linker is relatively mature and is typically much faster and uses much
less memory.
It is generally reliable, but may fail on certain projects (e.g. as a result of
linking to a shared library that only works with the default linker).
The llvm linker (lld) potentially offers even better performance, but it is
not as mature and may not work in all cases.
启用替代链接器遵循与 类似的模式-gsplit-dwarf,只不过需要设置链接器选项而不是编译器选项。还可以为所有构建类型设置相关选项,因为链接器通常会提供更好的性能,因此没有真正的理由仅将它们用于调试构建。假设相关链接器已安装并受所使用的工具链支持,则可以使用-fuse-ld=gold或启用替代链接器。-fuse-ld=lld如果开发人员通过 CMake 缓存设置,则链接器选项应附加到CMAKE_EXE_LINKER_FLAGS,CMAKE_MODULE_LINKER_FLAGS和
CMAKE_SHARED_LINKER_FLAGS变量。如果通过工具链文件设置,_INIT则应设置名称附加的相同变量。
Enabling an alternative linker follows a similar pattern to -gsplit-dwarf,
except that a linker option needs to be set rather than a compiler option.
The relevant option can also be set for all build types, since the linkers give
better performance in general, so there’s no real reason to only use them for
debug builds.
The alternative linkers can be enabled with -fuse-ld=gold or -fuse-ld=lld,
assuming the relevant linker is installed and supported by the toolchain being
used.
If set by the developer through the CMake cache, the linker option should be
appended to the CMAKE_EXE_LINKER_FLAGS, CMAKE_MODULE_LINKER_FLAGS and
CMAKE_SHARED_LINKER_FLAGS variables.
If being set via a toolchain file, the same variables with _INIT appended to
their names should be set.
如果项目想要设置链接器选项,则应再次遵循第 15 章“编译器和链接器要点”中给出的建议,并修改目录属性而不是变量。add_link_options()使用 CMake 3.13 或更高版本提供的命令最容易完成此操作:
If the project wants to set the linker option, again it should follow the
advice given in Chapter 15, Compiler And Linker Essentials and modify directory
properties rather than variables.
This is most easily done with the add_link_options() command available with
CMake 3.13 or later:
# Force the gold linker
add_link_options(-fuse-ld=gold)# Force the gold linker
add_link_options(-fuse-ld=gold)
通常,目标的依赖项必须在目标本身之前构建,但在某些情况下可以放宽甚至完全删除此约束。考虑以下示例:
Ordinarily, a target’s dependencies must be built before the target itself, but there are cases where this constraint can be relaxed or even removed entirely. Consider the following example:
cmake_minimum_required(VERSION 3.9)
project(simple LANGUAGES CXX)
add_library(Func func.cpp)
add_executable(App main.cpp)
target_link_libraries(App PRIVATE Func)cmake_minimum_required(VERSION 3.9)
project(simple LANGUAGES CXX)
add_library(Func func.cpp)
add_executable(App main.cpp)
target_link_libraries(App PRIVATE Func)
int func() { return 42; }int func() { return 42; }
int func();
int main() { return func(); }int func();
int main() { return func(); }
Unix Makefiles生成器将编译func.cpp、创建libFunc.a
静态归档库、编译main.cpp并最终链接App
可执行文件。这些任务都不能并行执行,它们必须按顺序执行。从 CMake 3.9 开始,Ninja生成器识别main.cpp可以编译而无需等待libFunc.a构建。在这种情况下,编译不依赖于libFunc.a。仅当没有与Func目标关联的自定义命令时,才可能实现这种放宽。即使在这种温和的条件下, Ninja
生成器由于这种放松而实现的并行性的提高对于某些项目来说也可能非常重要。
The Unix Makefiles generator will compile func.cpp, create the libFunc.a
static archive library, compile main.cpp and finally link the App
executable.
None of these tasks can be performed in parallel, they must be performed in
that order.
Since CMake 3.9, the Ninja generator recognizes that main.cpp can be
compiled without waiting for libFunc.a to be built.
Compilation does not depend on libFunc.a in this case.
This relaxation is only possible when there are no custom commands associated
with the Func target.
Even with this mild condition, the improved parallelism that the Ninja
generator can achieve as a result of this relaxation can be significant for
some projects.
CMake 3.19 添加了对OPTIMIZE_DEPENDENCIES目标属性的支持。当在静态或对象库目标上将其设置为 true 时,它允许在某些条件下进一步放松(对于所有 CMake 生成器类型,而不仅仅是 Ninja)。考虑这个例子:
CMake 3.19 added support for the OPTIMIZE_DEPENDENCIES target property.
When this is set to true on a static or object library target, it allows
further relaxation under certain conditions (for all CMake generator types,
not just Ninja).
Consider this example:
cmake_minimum_required(VERSION 3.19)
project(moreRelaxed LANGUAGES CXX)
add_library(MyStatic STATIC func.cpp)
add_library(MyShared SHARED impl.cpp)
target_link_libraries(MyStatic PRIVATE MyShared)
set_target_properties(MyStatic PROPERTIES
OPTIMIZE_DEPENDENCIES YES
)cmake_minimum_required(VERSION 3.19)
project(moreRelaxed LANGUAGES CXX)
add_library(MyStatic STATIC func.cpp)
add_library(MyShared SHARED impl.cpp)
target_link_libraries(MyStatic PRIVATE MyShared)
set_target_properties(MyStatic PROPERTIES
OPTIMIZE_DEPENDENCIES YES
)
int impl();
int func() { return impl(); }int impl();
int func() { return impl(); }
int impl() { return 42; }int impl() { return 42; }
在这种情况下,MyStatic从技术上讲,目标可以完全构建,而无需编译impl.cpp或生成共享库MyShared。这是因为它MyStatic是一个静态库,所以它不需要实际链接到MyShared. 两个库目标之间的关系仍然存在,并将应用于链接到 的任何其他目标MyStatic,但它不会影响构建MyStatic本身。
In this case, the MyStatic target can technically be fully built without
having to compile impl.cpp or produce a shared library for MyShared at
all.
This is because MyStatic is a static library, so it doesn’t need to actually
link to MyShared.
The relationship between the two library targets still exists and will be
applied to any other target that links to MyStatic, but it doesn’t affect
building MyStatic itself.
仅当 CMake 可以确定构建静态或对象库不需要构建依赖项时,才能执行此特定的依赖项放宽。防止这种情况发生的因素包括:
This particular dependency relaxation can only be performed when CMake can be certain that building the static or object library does not require the dependency to be built. Examples of things that prevent this from occurring include:
add_dependencies()指定依赖性的显式命令。
add_dependencies() command specifying the dependency.
OPTIMIZE_DEPENDENCIES。
OPTIMIZE_DEPENDENCIES target
property.
与在每个目标上显式指定属性相比,在项目范围内OPTIMIZE_DEPENDENCIES设置变量通常会更方便
。CMAKE_OPTIMIZE_DEPENDENCIES该变量用于OPTIMIZE_DEPENDENCIES在创建目标时初始化属性。
Rather than explicitly specifying the OPTIMIZE_DEPENDENCIES property on each
target, it will usually be more convenient to set the
CMAKE_OPTIMIZE_DEPENDENCIES variable project-wide.
This variable is used to initialize the OPTIMIZE_DEPENDENCIES property when
creating targets.
在尝试优化构建性能之前,始终关注构建的正确性。确保目标之间的关系得到正确表达,并且自定义命令中的依赖关系完全捕获所有依赖关系。指定不足的依赖项可能会导致构建失败,或者更糟糕的是,由于一项任务没有等待在当前构建中重新生成该输出,因此默默地使用先前构建的旧二进制输出。众所周知,此类问题很难追踪,在被诊断为不可靠构建的根本原因之前,通常很长一段时间都未被发现。在解决此类问题之前添加某些构建优化可能只会使构建更加不可靠。相反,通过不在目标之间添加不必要的依赖关系,构建工具有更大的机会并行执行部分构建并缩短总体构建时间。
Always focus on build correctness before trying to optimize build performance. Ensure that relationships between targets are properly expressed and that dependencies in custom commands fully capture all dependencies. Under-specified dependencies can lead to build failures, or worse still, silently using an old binary output from a previous build due to one task not waiting for that output to be regenerated in the current build. Such problems can be notoriously hard to trace, often going undetected for a very long time before being diagnosed as the underlying cause of unreliable builds. Adding certain build optimizations into the mix before addressing such problems will likely only make the build even more unreliable. Conversely, by not adding unnecessary dependencies between targets, the build tool has a greater opportunity to execute parts of the build in parallel and shorten the overall build time.
一种简单且相对安全的构建优化是对静态和对象库目标的依赖项进行项目范围的优化。CMAKE_OPTIMIZE_DEPENDENCIES这可以通过在顶级CMakeLists.txt文件、工具链文件或通过缓存变量中将变量设置为 true 来完成。使用 CMake 3.19 或更高版本时,这可以放松静态目标和对象库目标的依赖关系。这可能会提高构建并行性或减少日常开发期间必须构建的内容。早期的 CMake 版本将简单地忽略该变量。
One simple and relatively safe build optimization is to enable project-wide
optimization of dependencies for static and object library targets.
This can be done by setting the CMAKE_OPTIMIZE_DEPENDENCIES variable to
true in the top level CMakeLists.txt file, a toolchain file or via a cache
variable.
When using CMake 3.19 or later, this can relax dependency relationships for
static and object library targets.
This may improve build parallelism or reduce what has to be built during
day-to-day development.
Earlier CMake versions will simply ignore the variable.
当寻求优化构建性能时,请谨慎假设将执行构建的机器的特性。调整一台机器的统一构建批量大小等值可能会导致另一台机器性能差异很大(例如内存较少或 CPU 数量不同)。最好将这种调整留给开发人员使用缓存变量,除非项目已经知道上限,超出该上限则几乎不会获得任何收益(例如,源文件已经非常大)。谨慎使用GROUP统一模式,并且仅在它可以显着提高构建性能的情况下使用。
When seeking to optimize build performance, be careful about assuming
characteristics of the machine on which the build will be performed.
Tuning values like the unity build batch size for one machine may result in
poor performance on another with very different characteristics (e.g. less
memory or a different number of cpus).
Prefer to leave such tuning up to the developer using cache variables, except
for situations where the project already knows upper limits beyond which little
gain can be expected (e.g. source files that are already very large).
Use the GROUP unity mode sparingly and only where it provides a measurable
improvement in build performance.
在考虑项目的统一构建时,应从在项目范围内启用统一构建的角度出发,而不是尝试针对特定目标启用它。相反,重点是识别那些与统一构建不兼容的目标或源文件,并仅在这些实体上禁用它。这确保了分层构建有机会在整个依赖树中尽可能广泛地启用统一构建。
When considering unity builds for a project, start from the point of view that unity builds will be enabled project-wide rather than trying to turn it on for specific targets. Focus instead on identifying those targets or source files that are incompatible with a unity build and disable it on just those entities. This ensures hierarchical builds have the opportunity to enable unity builds as widely as possible throughout the dependency tree.
无论编译器是否支持预编译头,目标都应该成功构建。它应该被视为一种优化,而不是一项要求。特别是,不要stdafx.h在源代码中显式包含预编译头(例如 ),而是让 CMake 在编译器命令行上强制包含自动生成的预编译头。这在主要编译器之间更具可移植性,并且可能更容易维护。它还可以避免某些代码检查工具(包括您使用的工具)生成警告iwyu。
Targets should build successfully with or without compiler support for
precompiled headers.
It should be considered an optimization, not a requirement.
In particular, do not explicitly include a precompile header (e.g. stdafx.h)
in the source code, let CMake force-include an automatically generated
precompile header on the compiler command line instead.
This is more portable across the major compilers and is likely to be easier to
maintain.
It will also avoid warnings being generated from certain code checking tools
like iwyu (include what you use).
当预编译集中的标头很少更改时,预编译标头最有效。对于系统或编译器提供的标头通常是这样,但对于项目提供的标头可能并非如此。如果将项目标头添加到预编译集中,请测量效果并确认它提供了有价值的好处。避免添加经常更改的标头,因为这往往会对构建时间产生总体负面影响,因为当这些标头更改时,重建范围会增加。
Precompiled headers are most effective when the headers in the precompile set rarely change. This is typically true for headers provided by the system or the compiler, but may not be the case for headers provided by the project. If adding project headers to the precompile set, measure the effect and confirm that it gives a worthwhile benefit. Avoid adding headers that change often, as this will tend to have an overall negative effect on build times due to the increased scope of rebuilds when those headers change.
如果可能,请ccache在开发人员机器和持续集成机器上使用。它是减少增量构建时间和使用相同设置编译编译器经常看到的代码的最有效方法之一(持续集成构建的常见特征)。缓存未命中可能会导致该文件的编译时间增加约 10-30%,但典型开发或持续集成工作负载中缓存命中的增益足以弥补这一点(数量级的改进并不罕见) 。
Where possible, use ccache on both developer and continuous integration
machines.
It is one of the most effective ways for reducing incremental build times and
compiling code seen frequently by the compiler with the same settings (a common
characteristic of continuous integration builds).
Cache misses can result in an increase in compile time for that file of
around 10-30%, but the gain for cache hits in typical development or continuous
integration workloads more than makes up for that (order-of-magnitude
improvements are not unusual).
配置ccache将其缓存存储在 SSD 等快速磁盘上,因为该存储的延迟会对缓存性能产生很大影响。还将最大缓存大小增加到远高于默认值 5Gb,因为对于大多数实际部署而言,它通常太低。确保缓存草率选项包括pch_defines和time_macros如果使用预编译头。
Configure ccache to store its cache on a fast disk such as a SSD, as the
latency of that storage can have a strong effect on the cache performance.
Also increase the maximum cache size well above the 5Gb default, since it is
typically much too low for most real-world deployments.
Ensure the cache sloppiness options include pch_defines and time_macros if
using precompiled headers.
对于任何中等规模的项目,如果工具链支持,请考虑启用 split dwarf。这可以大大减少调试构建所消耗的磁盘空间,可以加快链接速度,并可以减少某些链接器使用的内存。与 split dwarf 一起使用时ccache,请确保使用最新版本ccache
(3.7 或更高版本)。旧版本很容易使用过时的.dwo文件或产生更多的缓存未命中。他们过去也无法正确处理预编译头和分割矮人的组合。
For any modest size project, consider enabling split dwarf if it is supported
by the toolchain.
This can drastically reduce disk space consumed by debug builds, it can speed
up linking and it can reduce memory used by some linkers.
When using ccache with split dwarf, ensure that a recent version of ccache
is used (3.7 or later).
Older versions were susceptible to using out-of-date .dwo files or generating
more cache misses.
They also used to fail to handle the combination of precompiled headers and
split dwarf properly.
如果项目有可能成为另一个项目的子依赖项,请避免强制使用非默认链接器。父项目可能有限制,要求它使用默认链接器,因此它必须有一种方法来防止子项目尝试使用其他东西。一个好的策略是仅尝试更改链接器(如果它是构建中的顶级项目),或者将其留给工具链文件来指定。这遵循一般建议,即开发人员应该能够启用或禁用大多数与工具链相关的功能,除非项目的实际要求除外。
Avoid forcing the use of a non-default linker if there is any chance that the project may become a child dependency of another project. A parent project may have restrictions which require it to use the default linker, so it must have a way to prevent the child project from trying to use something else. A good strategy is to only try to change the linker if it is the top level project in the build, or leave it for a toolchain file to specify. This follows the general recommendation that the developer should be able to enable or disable most toolchain-related features except where it is an actual requirement of a project.
Qt 享受 CMake 的直接支持已有一段时间了。这种支持在这两个项目的许多版本中都得到了发展,可以大致分为以下三个主要领域:
Qt has enjoyed direct support in CMake for some time. This support has evolved over many releases of both projects and can loosely be grouped under the following three broad areas:
moc,例如uic、 和。rcc
moc, uic and rcc as needed during the build.
对于 Qt 4 及更早版本,对这些功能的支持由 CMake 独家提供。从 Qt 5 开始,大部分功能由 Qt 本身提供,CMake 仅处理 Qt 工具支持的一些更自动化的方面。人们越来越期望项目使用 Qt 5 而不是早期版本。Qt 6.0最近也发布了,但其CMake API仍在开发中。考虑到这些事情,本章主要关注 Qt 5 支持。
For Qt 4 and earlier, support for these features was provided exclusively by CMake. Starting with Qt 5, most of the features are provided by Qt itself, with CMake only handling some of the more automated aspects of the Qt tool support. Projects are increasingly expected to be using Qt 5 rather than earlier versions. Qt 6.0 was also released recently, but its CMake API is still undergoing development. With these things in mind, this chapter focuses mostly on Qt 5 support.
CMake 3.5 应被视为方便 Qt 5 支持所需的最低版本,但强烈建议如果可能的话使用更新的 CMake 版本,最好是最新版本。
CMake 3.5 should be considered the bare minimum version required for convenient Qt 5 support, but it is strongly advised to use a more recent CMake release if possible, ideally the latest.
任何使用Qt的项目都需要找到合适的Qt安装。考虑到 Qt 的规模,从源代码构建 Qt 作为项目的一部分通常是不切实际的。相反,包通常从 Qt 官方网站、包管理器获取或预先从源单独构建。
Any project that uses Qt needs to find a suitable Qt installation. Given Qt’s size, it is not generally practical to build Qt from source as part of the project. Instead, packages are usually obtained from the official Qt website, a package manager or built separately from sources beforehand.
将 Qt 引入项目构建的最常见方法是使用
find_package()搜索Qt5包并指定项目需要哪些 Qt 组件。以下示例显示如何需要Gui和Widgets组件,但保留DBus为可选组件。它还指定需要 Qt 5.9 或更高版本。
The most common way to bring Qt into the project’s build is to use
find_package() to search for the Qt5 package and specify which Qt
components the project needs.
The following example shows how to require the Gui and Widgets components,
but leave DBus as an optional component.
It also specifies that Qt 5.9 or later is needed.
find_package(Qt5 5.9 REQUIRED
COMPONENTS Gui Widgets
OPTIONAL_COMPONENTS DBus
)find_package(Qt5 5.9 REQUIRED
COMPONENTS Gui Widgets
OPTIONAL_COMPONENTS DBus
)
在许多情况下,Qt 不会安装在 CMake 的默认搜索位置之一。开发人员通常需要使用
CMAKE_PREFIX_PATH缓存变量向 CMake 提供该信息。当从 Qt Creator 等环境运行 CMake 时,必须在项目设置中进行设置。从命令行运行 CMake 时,可以按照通常的方式完成,如下所示:
In many cases, Qt won’t be installed in one of CMake’s default search locations.
Developers will typically need to provide that information to CMake using the
CMAKE_PREFIX_PATH cache variable.
When running CMake from an environment like Qt Creator, this has to be set in
the project settings.
When running CMake from the command line, it can be done in the usual way, like
so:
cmake -D CMAKE_PREFIX_PATH=/pathToQt/5.12.0/clang_64 \
/路径/myProjectcmake -D CMAKE_PREFIX_PATH=/pathToQt/5.12.0/clang_64 \
/pathTo/myProject如果成功,find_package()调用将为找到的每个组件定义一个导入目标。这些导入的目标将被命名Qt5::<ModuleName>,例如Qt5::Gui,
Qt5::Widgets等。当项目目标链接到这些导入的目标时,它们将自动添加相关的编译器定义、标头搜索路径和库依赖项。因此,定义使用 Qt 的目标通常应该非常简单:
If successful, the find_package() call will define an imported target for
each component found.
These imported targets will be named Qt5::<ModuleName>, such as Qt5::Gui,
Qt5::Widgets, etc.
When project targets link against these imported targets, they will
automatically have the relevant compiler defines, header search paths and
library dependencies added to them.
As a result, defining targets that use Qt should generally be quite
straightforward:
find_package(Qt5 5.9 REQUIRED COMPONENTS Widgets)
add_executable(SimpleGUI MACOSX_BUNDLE WIN32 main.cpp)
target_link_libraries(SimpleGUI PRIVATE Qt5::Widgets)find_package(Qt5 5.9 REQUIRED COMPONENTS Widgets)
add_executable(SimpleGUI MACOSX_BUNDLE WIN32 main.cpp)
target_link_libraries(SimpleGUI PRIVATE Qt5::Widgets)
或者,可以通过单独调用来独立找到每个 Qt 组件find_package():
Alternatively, each Qt component could be found independently with separate
calls to find_package():
find_package(Qt5Widgets 5.9 REQUIRED)
find_package(Qt5Gui 5.9 REQUIRED)
find_package(Qt5DBus 5.9)find_package(Qt5Widgets 5.9 REQUIRED)
find_package(Qt5Gui 5.9 REQUIRED)
find_package(Qt5DBus 5.9)
这是 Qt 文档所建议的,但它可能允许混合来自不同 Qt 安装的库。通常不鼓励这样做,因为它可能会使用一组不一致的库,并且可能难以跟踪错误。使用一次find_package()调用来查找伞Qt5包更安全,并且更清楚地传达了项目对组件处理的意图。
This is what the Qt documentation recommends, but it potentially allows mixing
libraries from different Qt installations.
This would generally be discouraged, since it opens up the possibility of using
an inconsistent set of libraries and potentially hard to trace bugs.
Using one find_package() call to find the umbrella Qt5 package is safer and
more clearly communicates the project’s intent for component handling.
无论是Qt5单独查找伞包还是单独的组件,都将定义相同的一组导入目标和变量。一般来说,项目应该更喜欢使用导入的目标而不是变量,但是有几个可能有用的每个组件变量:
Regardless of whether finding the Qt5 umbrella package or the individual
components separately, the same set of imported targets and variables will be
defined.
In general, projects should prefer to work with the imported targets rather
than the variables, but there are a couple of per-component variables which may
be useful:
Qt5<component>_FOUND
Qt5<component>_FOUND
Qt5<component>_VERSION
Qt5<component>_VERSION
一些 Qt 组件还定义了关联的 CMake 命令。其中一些将在下面第 31.3 节“Autogen”中进一步讨论。
Some of the Qt components also define associated CMake commands. A number of these are discussed in Section 31.3, “Autogen” further below.
大多数项目应该能够链接到相关的导入目标,而不必过多关注编译器和链接器的详细信息。也就是说,一些重要的、不太明显的领域值得特别关注。
Most projects should be able to link to the relevant imported targets and not have to concern themselves too much with the compiler and linker details. That said, some important, less obvious areas deserve special attention.
Qt 6.0 及更高版本需要支持 C++17 的编译器。Qt 5.7 及更高版本需要 C++11,Qt 5.6 可选择支持 C++11。这引起了人们对一个微妙但重要的问题的注意,即该项目应该使用与构建 Qt 相同的 C++ 标准库。如果项目没有定义任何编译器功能要求或任何最低语言标准,则 Qt 导入的目标将通过接口属性传递设置相关详细信息。该项目通常还会指定语言标准,也许还指定整个构建的标准库实现。因此,项目应该小心确保其选择的语言标准和标准库实现与Qt库使用的一致。
Qt 6.0 and later require a compiler that supports C++17. Qt 5.7 and later requires C++11 and Qt 5.6 optionally supports C++11. This draws attention to a subtle but important issue, that the project should use the same C++ standard library as was used to build Qt. If the project does not define any compiler feature requirements or any minimum language standard, then the Qt imported targets will transitively set the relevant details via interface properties. The project would also normally specify the language standard and perhaps the standard library implementation for the whole build. Therefore, the project should be careful to ensure that its chosen language standard and standard library implementation are consistent with those used by the Qt libraries.
需求不一致的一个常见示例是使用 Qt 5 但使用 C++17 功能的项目。它可能会设置其目标CXX_STANDARD和CXX_STANDARD_REQUIRED
属性,或者可能会添加cxx_std_17为编译功能要求。这可能会导致 Qt 库链接到 C++11 的标准库,但项目链接到 C++17 的标准库。这对于某些库实现来说可能是安全的,但不能保证如此。
A common example of inconsistent requirements would be a project that uses
Qt 5 but makes use of C++17 features.
It might set its targets’ CXX_STANDARD and CXX_STANDARD_REQUIRED
properties, or it might add cxx_std_17 as a compile feature requirement.
This can result in the Qt libraries linking against a standard library for
C++11 but the project linking against a standard library for C++17.
This may be safe for some library implementations, but it is not guaranteed to
be so.
如果项目是使用不同的 C++ 标准构建的,甚至只是使用与构建 Qt 时使用的编译器版本不同的编译器版本,则无法保证保持二进制兼容性。实际上,对于主流编译器来说,如果满足以下所有条件通常是安全的:
If the project is built with a different C++ standard or even just a different compiler version than was used to build Qt, there is no guarantee that binary compatibility will be maintained. In practice, for the mainstream compilers it is typically safe if all of the following are true:
正如第 21.6 节“混合静态库和共享库”中提到的,共享库要求在某些平台上将源代码编译为位置无关代码 (PIC)。这与运行时的符号重定位有关,并由 CMake 通过 target 属性自动处理POSITION_INDEPENDENT_CODE。在此类平台上,程序有时也可以构建为与位置无关的可执行文件 (PIE)。这通过 ASLR(地址空间布局随机化)使符号地址的可预测性降低,从而增加了一层安全性。
As mentioned back in Section 21.6, “Mixing Static And Shared Libraries”, shared libraries
require sources to be compiled as position-independent code (PIC) on some
platforms.
This relates to symbol relocation at runtime and is handled automatically by
CMake through the POSITION_INDEPENDENT_CODE target property.
On such platforms, programs can also sometimes be built as position-independent
executables (PIE).
This adds a layer of security by making addresses of symbols less predictable
through ASLR (Address Space Layout Randomization).
对于 Qt 应用程序,PIE 可能会带来一些挑战。当在 ELF 平台(例如基于 x86 的 Linux 系统)上使用 GCC 进行构建时,可执行文件不得使用该标志进行编译-fPIE,否则可能会因 Qt 使用符号的方式而发生崩溃。-fPIC经过 GCC 和 Qt 开发人员之间的广泛讨论,提出了使用替代方案作为解决方法的建议。这是一种不寻常的安排,-fPIC通常仅用于库,而不是可执行文件。
For Qt applications, PIE can present some challenges.
When building with GCC on ELF platforms (e.g. x86-based Linux systems),
executables must not be compiled with the -fPIE flag or crashes will likely
occur due to the way Qt uses symbols.
Following extensive discussions between GCC and Qt developers, a recommendation
to use -fPIC instead was put forward as a workaround.
This is an unusual arrangement, -fPIC is typically only used for libraries,
not executables.
CMake 遵循这些选项的典型用法,并在编译目标属性设置为 true 的-fPIE可执行目标的源时
添加(有一些警告 - 请参阅模块和策略,这两者都是在 CMake 3.14 中添加的)。这与 Qt 应用程序的独特需求不兼容。对于那些可执行目标,项目应确保
目标属性未设置为 true。如果项目需要 Qt 应用程序的 ASLR,则必须将编译器标志添加到可执行目标。POSITION_INDEPENDENT_CODECheckPIESupportedCMP0083POSITION_INDEPENDENT_CODE-fPIC
CMake follows the typical usage of these options and adds -fPIE when
compiling sources of executable targets that have their
POSITION_INDEPENDENT_CODE target property set to true (with some caveats — see the CheckPIESupported module and CMP0083 policy, both of which were
added in CMake 3.14).
This is incompatible with the unique needs of Qt applications.
For those executable targets, projects should ensure the
POSITION_INDEPENDENT_CODE target property is not set to true.
If a project wants ASLR for a Qt application, the -fPIC compiler flag must
be added to the executable target.
从 Qt 5.14 开始,目标通过其属性-fPIC自动强制添加该标志
。因此,针对 Qt 5.14 或更高版本构建的项目不需要特殊步骤。使用早期的 Qt 版本时,项目必须使用或其他等效方法自行添加到可执行目标。请注意,这仅适用于可执行目标,
目标属性将为库目标生成正确的编译器标志。Qt5::CoreINTERFACE_COMPILE_OPTIONS-fPICtarget_compile_options()POSITION_INDEPENDENT_CODE
From Qt 5.14, adding the -fPIC flag is automatically enforced by the
Qt5::Core target through its INTERFACE_COMPILE_OPTIONS property.
Therefore, no special steps are required for projects building against
Qt 5.14 or later.
When using earlier Qt versions, projects must add -fPIC to their executable
targets themselves using target_compile_options() or some other equivalent
method.
Note that this only applies to executable targets, the
POSITION_INDEPENDENT_CODE target property will yield the correct compiler
flags for library targets.
Windows 的一个独特之处是 GUI 和控制台程序具有不同的入口点。控制台应用程序使用main(),就像其他平台一样,但 Windows GUI 应用程序使用WinMain()作为入口点。为了使项目不必处理这种差异,Qt 提供了一个特殊的库,它定义了一个WinMain()转发到main(). Qt5::Core如果链接到的可执行文件Qt5::Core是 GUI 应用程序,则导入的目标将自动链接到此特殊库中。targetWIN32_EXECUTABLE属性控制此选择,最常见的是通过将WIN32关键字添加到add_executable()调用来设置。
An aspect unique to Windows is that GUI and console programs have different
entry points.
Console applications use main(), just like other platforms, but Windows GUI
applications use WinMain() as their entry point.
To free projects from having to handle this difference, Qt provides a special
library that defines a simple WinMain() function which forwards to main().
The Qt5::Core imported target will automatically link in this special library
if the executable linking to Qt5::Core is a GUI application.
The WIN32_EXECUTABLE target property controls this choice and is most
commonly set by adding the WIN32 keyword to the add_executable() call.
find_package(Qt5 5.9 REQUIRED Core Gui)
add_executable(ConsoleApp ...)
add_executable(GuiApp WIN32 ...)
target_link_libraries(ConsoleApp PRIVATE Qt5::Core)
target_link_libraries(GuiApp PRIVATE Qt5::Gui Qt5::Core)find_package(Qt5 5.9 REQUIRED Core Gui)
add_executable(ConsoleApp ...)
add_executable(GuiApp WIN32 ...)
target_link_libraries(ConsoleApp PRIVATE Qt5::Core)
target_link_libraries(GuiApp PRIVATE Qt5::Gui Qt5::Core)
在上面的例子中,ConsoleApp不会链接到特殊Qt5::WinMain
库,而GuiApp会。如本示例所示,项目不需要直接处理甚至提及Qt5::WinMain. 它是自动处理的。如果Qt5::WinMain需要禁用库的自动链接,则Qt5_NO_LINK_QTMAIN可以将 target 属性设置为 false。
In the above example, ConsoleApp will not link in the special Qt5::WinMain
library, whereas GuiApp will.
As this example shows, projects shouldn’t need to directly handle or even
mention Qt5::WinMain.
It is handled automatically.
In the event that this automatic linking of the Qt5::WinMain library needs
to be disabled, the Qt5_NO_LINK_QTMAIN target property can be set to
false.
Qt 提供了许多命令行工具,用于处理源代码并生成供构建使用的其他源文件。这些工具中最重要和最常用的三个是moc、uic和
rcc。CMake 提供了专用功能,可以自动化在项目中使用这些工具的方式。Qt 提供的 CMake 命令还允许项目以更手动、更精细的方式使用这些工具,以便在需要时进行更好的控制。
Qt provides a number of command-line tools which process sources and generate
other source files to be consumed by the build.
The three most important and heavily used of these tools are moc, uic and
rcc.
CMake provides dedicated features that automate the way these tools are used
within a project.
CMake commands provided by Qt also allow projects to use these tools in a more
manual, granular way for greater control if required.
CMake 3.8 改变了 Qt 相关自动生成文件的处理方式。这些文件现在默认生成到它们自己的单独目录中,并且该目录会在需要时自动添加到标头搜索路径等内容中。使用 CMake 3.7 或更早版本时,项目必须采取额外步骤来考虑此类搜索路径。以下部分讨论这些更改如何影响每个工具。
CMake 3.8 changed the way Qt-related auto-generated files are handled. Those files are now generated into their own separate directory by default and that directory is automatically added to things like the header search paths where needed. When using CMake 3.7 or earlier, projects must take extra steps to account for such search paths. The following sections discuss how these changes affect each of the tools.
Qt 使用自己的元对象系统来实现信号和槽、运行时类型信息以及自己的属性系统。它提供了moc扫描源代码以查找特定宏(例如Q_OBJECT,Q_GADGET等等)并生成实现宏声明的底层函数的 C++ 代码的工具。以下是需要处理的类的典型示例moc:
Qt uses its own meta-object system to implement signals and slots, run-time
type information and its own property system.
It provides the moc tool which scans source code looking for particular
macros (e.g. Q_OBJECT, Q_GADGET and so on) and generates C++ code
implementing the underlying functions the macros declare.
The following is a typical example of a class that requires processing by moc:
class SomeClass : public QObject
{
Q_OBJECT
public:
SomeClass(QObject* parent);
~SomeClass();
// ... various signals, slots,
// properties, member functions, etc....
};class SomeClass : public QObject
{
Q_OBJECT
public:
SomeClass(QObject* parent);
~SomeClass();
// ... various signals, slots,
// properties, member functions, etc....
};
CMake 提供了一种完全自动化的方法来管理moc在给定目标的源文件上运行的方式和时间。通过将 target 属性设置为 true 可以启用此功能AUTOMOC。CMAKE_AUTOMOC
该属性是在创建目标时根据变量的值进行初始化的。启用后AUTOMOC,将在构建时扫描文件并moc在相关文件上运行。生成的代码将根据项目和源文件的结构以适当的方式合并到目标中。如果需要将额外的命令行选项传递给工具moc,可以在目标属性中指定它们AUTOMOC_MOC_OPTIONS。
CMake provides a fully automated way of managing how and when moc should be
run on source files of a given target.
This functionality is enabled by setting the AUTOMOC target property to
true.
The property is initialized from the value of the CMAKE_AUTOMOC
variable when the target is created.
When AUTOMOC is enabled, files are scanned at build time and moc is run on
the relevant files.
The generated code will be incorporated into the target in the appropriate way
depending on how the project and source files are structured.
If extra command line options need to be passed to the moc tool, they can be
specified in the AUTOMOC_MOC_OPTIONS target property.
在最简单的场景中,像上面这样的类在标头中定义并使用宏Q_OBJECT。没有其他项目源文件对将生成的文件名做出任何假设
moc。该项目AUTOMOC负责编译生成的文件并将它们链接到目标中。这设置起来非常简单,并且可以AUTOMOC最大程度地控制将生成的代码添加到构建中的方式。
In the simplest scenario, a class like the one above is defined in a header and
uses the Q_OBJECT macro.
No other project source files make any assumption about the file name(s) that
moc will generate.
The project lets AUTOMOC take care of compiling the generated files and
linking them into the target.
This is very simple to set up and gives AUTOMOC the most control over the way
the generated code is added to the build.
set(CMAKE_AUTOMOC YES)
add_executable(MyQtApp
main.cpp
myclass.cpp
myclass.h
)
find_package(Qt5 COMPONENTS Core REQUIRED)
target_link_libraries(MyQtApp PRIVATE Qt5::Core)set(CMAKE_AUTOMOC YES)
add_executable(MyQtApp
main.cpp
myclass.cpp
myclass.h
)
find_package(Qt5 COMPONENTS Core REQUIRED)
target_link_libraries(MyQtApp PRIVATE Qt5::Core)
上述方法的一个缺点是所有生成的源可能会与更新的 CMake 版本合并到单个实现文件中。如果这种方式处理的类数量AUTOMOC非常大,会给编译器资源带来压力,极端情况下甚至会导致构建失败。至少,它会使较大的构建效率降低,因此可能需要单独编译生成的文件,以利用构建并行性并减少资源需求。实现此目的的一种方法是让项目源显式包含生成的源本身,而不是将AUTOMOC它们添加到构建中。目标可以有一个 C++ 源文件,其中包含如下行(basename在本示例中只是占位符):
One weakness of the above approach is that all generated sources may be
combined into a single implementation file with more recent CMake versions.
If the number of classes processed by AUTOMOC in this way is very large, it
can put pressure on compiler resources and even cause build failure in extreme
cases.
At the very least, it can make larger builds less efficient, so it may be
desirable to have the generated files compiled individually to take advantage
of build parallelism and reduce resource requirements.
One way to achieve this is for project sources to explicitly include the
generated sources themselves rather than leaving AUTOMOC to add them to the
build.
A target can have a C++ source file which contains a line like the following
(basename is just a placeholder in this example):
#include "moc_basename.cpp"#include "moc_basename.cpp"
如果找到名为 的文件basename.h(或具有任何其他可识别的头文件扩展名),则将在该标头上AUTOMOC运行moc,以便生成一个名为moc_basename.cpp. 由于实现文件已经通过
#include语句提取了生成的文件,AUTOMOC因此无需执行任何其他操作即可将其添加到构建中。它已经被编译为basename.cpp. 换句话说,添加包含 formoc_basename.cpp会使该源文件接管将生成的文件添加到构建中,而不是将AUTOMOC
其与其他生成的文件组合。另一个优点是它与 CMake 的统一构建支持非常自然地集成(请参阅第 30.1 节“Unity 构建”)。生成的文件实际上成为
其所属文件moc_basename.cpp的一部分。basename.cppUnity 构建对于将有多少目标源合并到一批中具有可配置的限制,因此这也将间接限制
将有多少生成的moc_*.cpp文件合并到一个编译单元中。
If a file called basename.h is found (or with any other recognized header
file extension), then AUTOMOC will run moc on that header such that it
generates a file called moc_basename.cpp.
Since the implementation file already pulls in that generated file via an
#include statement, AUTOMOC doesn’t have to do anything more to add it to
the build.
It will already be compiled as part of basename.cpp.
In other words, adding an include for moc_basename.cpp makes that source file
take over adding the generated file to the build instead of letting AUTOMOC
combine it with other generated files.
An added advantage is that it integrates quite naturally with CMake’s unity
build support (see Section 30.1, “Unity Builds”).
The generated moc_basename.cpp effectively becomes part of the basename.cpp
file it belongs to.
Unity builds have a configurable limit to how many target sources will be
merged into one batch, so this will indirectly limit how many generated
moc_*.cpp files get combined into one compilation unit as well.
在某些情况下,私有类可以在 C++ 实现文件中而不是在标头中声明。如果这样的类使用Q_OBJECT,moc则必须在该 C++ 文件上运行。考虑到这一点,如果源文件包含一行,其中包含.moc
如下所示的文件,即使它不是标头,AUTOMOC也会在该源文件上运行。moc在这种情况下,它将使用指定的文件名来生成代码:
In some cases, a private class may be declared in a C++ implementation file
rather than in a header.
If such a class uses Q_OBJECT, moc must be run on that C++ file.
To account for this, if a source file contains a line that includes a .moc
file like shown just below, AUTOMOC will run moc on that source file even
if it isn’t a header.
In such cases, it will use the specified file name for the generated code:
#include "basename.moc"#include "basename.moc"
同样,源文件自行提取生成的代码,因此AUTOMOC
无需执行任何其他操作即可将其添加到构建中。正如前面提到的,这对于统一构建具有相同的好处。
Again, the source file pulls in the generated code on its own, so AUTOMOC
doesn’t have to do anything more to add it to the build.
That has the same benefit for unity builds as mentioned previously.
默认情况下,AUTOMOC扫描宏Q_OBJECT、Q_GADGET和
Q_NAMESPACE。CMake 3.17Q_NAMESPACE_EXPORT也添加到此列表中。从 CMake 3.10 开始,此宏名称列表取自
目标属性,该属性是在创建目标时AUTOMOC_MACRO_NAMES从变量初始化的
。CMAKE_AUTOMOC_MACRO_NAMES对于大多数项目来说,这些默认值就足够了。AUTOMOC_MACRO_NAMES如果传统的宏名称嵌入到包装宏中,则还需要添加这些包装宏的名称。下面的示例演示了这种情况:
By default, AUTOMOC scans for the macros Q_OBJECT, Q_GADGET and
Q_NAMESPACE.
CMake 3.17 added Q_NAMESPACE_EXPORT to this list as well.
From CMake 3.10, this list of macro names is taken from the
AUTOMOC_MACRO_NAMES target property, which is initialized from the
CMAKE_AUTOMOC_MACRO_NAMES variable when the target is created.
For most projects, these defaults are fine.
If the conventional macro names are embedded within wrapper macros, the names
of those wrapper macros need to be added to AUTOMOC_MACRO_NAMES as well.
The following contrived example demonstrates such a scenario:
#define MY_WRAPPER Q_OBJECT#define MY_WRAPPER Q_OBJECT
#include mywrapper.h
class SomeClass : public QObject
{
MY_WRAPPER
public:
// ...
};#include mywrapper.h
class SomeClass : public QObject
{
MY_WRAPPER
public:
// ...
};
对于这种情况,项目可以通过将包装器宏添加到全局默认值来最方便地处理此问题,如下所示:
For such a case, the project would most conveniently handle this by adding the wrapper macro to the global defaults like so:
list(APPEND CMAKE_AUTOMOC_MACRO_NAMES MY_WRAPPER)list(APPEND CMAKE_AUTOMOC_MACRO_NAMES MY_WRAPPER)
如果包装器宏引用应被视为生成依赖项的文件名moc,AUTOMOC_DEPEND_FILTERS则应使用目标属性来表达该依赖项。通常并不需要此属性,因此感兴趣的读者可以参阅该属性的官方 CMake 文档以获取更多详细信息。
If the wrapper macro refers to a file name that should be considered a
dependency for the moc generation, the AUTOMOC_DEPEND_FILTERS target
property should be used to express that dependency.
This property is not often needed, so the interested reader is referred to the
official CMake documentation for that property for further details.
使用 时AUTOMOC,CMake 3.8 及更高版本将在为此类自动生成的文件预留的单独目录中生成moc_*.cpp或
文件。*.moc该目录将自动添加到目标的标头搜索路径中,因此项目不需要执行任何其他操作。使用 CMake 3.7 或更早版本时,生成的文件是在当前构建目录中创建的,并且该目录不会自动添加到标头搜索路径中。因此,项目的常见做法是将
CMAKE_INCLUDE_CURRENT_DIR变量设置为 true,以便当前源目录和构建目录始终添加到标头搜索路径中。如果项目已经需要 CMake 3.8 或更高版本,则应避免设置,
CMAKE_INCLUDE_CURRENT_DIR因为在这种情况下不需要AUTOMOC。
When using AUTOMOC, CMake 3.8 and later will generate the moc_*.cpp or
*.moc files in a separate directory set aside for such auto-generated files.
That directory will be automatically added to the target’s header search path,
so the project doesn’t need to do anything else.
When using CMake 3.7 or earlier, the generated files are created in the current
build directory and that directory is not automatically added to the header
search path.
It is therefore common practice for projects to set the
CMAKE_INCLUDE_CURRENT_DIR variable to true so that the current source and
build directories are always added to the header search path.
If the project already requires CMake 3.8 or later, it should avoid setting
CMAKE_INCLUDE_CURRENT_DIR since it is not needed for AUTOMOC in that case.
在某些情况下,可能需要阻止moc处理特定文件。通过将该文件的SKIP_AUTOMOCsource 属性设置为 true 可以最轻松地实现这一点。如果其他 autogen 工具也喜欢uic并且rcc也应该跳过该文件,
SKIP_AUTOGEN则可以将 source 属性设置为 true。
In some situations, it may be necessary to prevent moc from processing a
particular file.
This is most easily achieved by setting that file’s SKIP_AUTOMOC source
property to true.
If other autogen tools like uic and rcc should also skip the file, the
SKIP_AUTOGEN source property can be set to true instead.
add_executable(MyApp noMocPlease.cpp noAutoGen.cpp ...)
set_source_files_properties(noMocPlease.cpp PROPERTIES
SKIP_AUTOMOC TRUE
)
set_source_files_properties(noAutoGen.cpp PROPERTIES
SKIP_AUTOGEN TRUE
)add_executable(MyApp noMocPlease.cpp noAutoGen.cpp ...)
set_source_files_properties(noMocPlease.cpp PROPERTIES
SKIP_AUTOMOC TRUE
)
set_source_files_properties(noAutoGen.cpp PROPERTIES
SKIP_AUTOGEN TRUE
)
虽然设置AUTOMOC为 true 是在相关源上运行的通常推荐的方法
moc,但在非常大的项目中,扫描许多源的性能成本可能并不小。如果只有某些源需要moc处理而其他源不需要处理,则项目可以使用组件提供的以下命令手动指定要处理的源,Qt5::Core而不是使用AUTOMOC:
While setting AUTOMOC to true is the generally recommended way of enabling
moc to be run on the relevant sources, in very large projects the performance
cost of scanning many sources may be non-trivial.
If only some sources need moc processing and others don’t, projects can
manually specify which ones to process with the following command provided by
the Qt5::Core component instead of using AUTOMOC:
qt5_wrap_cpp(outVar sources... [OPTIONS ...])qt5_wrap_cpp(outVar sources... [OPTIONS ...])
要处理的文件moc应列为sources. 生成的moc_*.cpp文件结果集将通过变量返回给调用者outVar。任何要传递给该工具的额外命令行选项都moc可以在关键字之后指定OPTIONS。该项目负责确保将生成的源添加到构建中,并且至少一个目标必须依赖于它们。
The files to be processed with moc should be listed as the sources.
The resultant set of generated moc_*.cpp files will be provided back to the
caller in the outVar variable.
Any extra command-line options to be passed to the moc tool can be specified
after the OPTIONS keyword.
The project is responsible for ensuring that the generated sources are added to
the build and at least one target must depend on them.
满足这些要求的最简单方法是将生成的源添加到可执行文件或库目标。例如:
The easiest way to satisfy these requirements is to add the generated sources to an executable or library target. For example:
find_package(Qt5 COMPONENTS Core REQUIRED)
# Process this file manually rather than using AUTOMOC
qt5_wrap_cpp(genMocs myclass.h)
add_executable(MyQtApp
main.cpp
myclass.cpp
myclass.h
${genMocs}
)find_package(Qt5 COMPONENTS Core REQUIRED)
# Process this file manually rather than using AUTOMOC
qt5_wrap_cpp(genMocs myclass.h)
add_executable(MyQtApp
main.cpp
myclass.cpp
myclass.h
${genMocs}
)
满足要求的另一种方法是在现有源文件之一中#include生成
moc_*.cpp文件,并创建依赖于 中列出的文件的自定义目标outVar。需要自定义目标来确保为自定义执行步骤创建构建规则moc。请注意,该qt5_wrap_cpp()命令将在当前构建目录中生成其输出文件,类似于AUTOMOCCMake 3.7 及更早版本的行为,因此CMAKE_INCLUDE_CURRENT_DIR如果使用此方法可能需要。这样的安排可能看起来像这样:
Another way to satisfy the requirements is to #include the generated
moc_*.cpp file within one of the existing source files and create a custom
target that depends on the files listed in outVar.
The custom target is needed to ensure that build rules are created for the
custom moc execution steps.
Note that the qt5_wrap_cpp() command will generate its output files in the
current build directory similar to the AUTOMOC behavior of CMake 3.7 and
earlier, so CMAKE_INCLUDE_CURRENT_DIR may be needed if using this method.
Such an arrangement may look something like this:
find_package(Qt5 COMPONENTS Core REQUIRED)
# Process this file manually rather than using AUTOMOC
qt5_wrap_cpp(genMocs myclass.h)
set(CMAKE_INCLUDE_CURRENT_DIR YES)
add_executable(MyQtApp
main.cpp
myclass.cpp
myclass.h
)
add_custom_target(MyMocs DEPENDS ${genMocs})find_package(Qt5 COMPONENTS Core REQUIRED)
# Process this file manually rather than using AUTOMOC
qt5_wrap_cpp(genMocs myclass.h)
set(CMAKE_INCLUDE_CURRENT_DIR YES)
add_executable(MyQtApp
main.cpp
myclass.cpp
myclass.h
)
add_custom_target(MyMocs DEPENDS ${genMocs})
#include "moc_myclass.cpp"
// Rest of the file as normal...#include "moc_myclass.cpp"
// Rest of the file as normal...
第一种方法比较简单,而第二种方法如果
AUTOMOC以前使用过并且已经将源文件直接修改为#include生成的moc_*.cpp文件,则可能会很有用。任何一种方法都应该以自然的方式与统一构建集成。
The first method is simpler, whereas the second method may be useful if
AUTOMOC had been used previously and the source files were already modified
to #include the generated moc_*.cpp files directly.
Either method should integrate with unity builds in a natural way.
Qt GUI 应用程序可以基于不同的 UI 技术。他们可以使用 QML 声明性语言、基于 C++QWidget层次结构的小部件,或者有时两者的组合。QML 不需要构建系统进行特殊处理,它纯粹由代码处理(.qml文件可能存储在资源中以供应用程序在运行时使用,并且构建系统可能需要处理资源)。小部件也可以纯粹用 C++ 代码实现,但也可以使用基于 XML 的 UI 描述文件来定义。这些 UI 文件通常具有.ui文件扩展名,并由工具处理
uic以生成 C++ 代码,然后像任何其他源文件一样编译到项目中。
Qt GUI applications can be based on different UI technologies.
They can use the QML declarative language, widgets based on the
C++ QWidget hierarchy, or sometimes a combination of both.
QML requires no special handling from the build system, it is handled purely
by the code (.qml files may be stored in resources for the application to use
at run time though and the build system may need to process the resources).
Widgets can be implemented purely in C++ code too, but they can also be
defined using a XML-based UI description file.
These UI files typically have a .ui file extension and are processed by the
uic tool to produce C++ code, which is then compiled into the project just
like any other source file.
该uic工具与 的有许多相似之处moc,无论是工具的工作方式还是可用的 CMake 支持。CMake 提供了AUTOUIC目标属性,其作用与AUTOMOC. 该属性是
在创建目标时AUTOUIC根据变量的值进行初始化的。CMAKE_AUTOUIC当AUTOUIC设置为 true 时,CMake 将在构建时扫描该目标的源文件,查找#include提取ui_basename.h. basename.ui当它找到此类语句时,它会在同一目录或该目标的 target 属性中列出的任何目录中
搜索匹配的文件AUTOUIC_SEARCH_PATHS。如果该#include语句在 之前有一个路径组件ui_basename.h,则无论有没有该路径都将执行搜索。如果basename.ui找到文件,将使用该uic工具对其进行处理以生成标头。
The uic tool has many similarities to moc, both in the way the tools work
and the CMake support available.
CMake provides the AUTOUIC target property which fulfills a very similar
role to AUTOMOC.
The AUTOUIC property is initialized from the value of the CMAKE_AUTOUIC
variable when the target is created.
When AUTOUIC is set to true, CMake will scan that target’s source files at
build time looking for #include statements that pull in header files of the
form ui_basename.h.
When it finds such statements, it searches for a matching basename.ui file
in the same directory or any directories listed in that target’s
AUTOUIC_SEARCH_PATHS target property.
If the #include statement has a path component before the ui_basename.h,
the search will be performed with and without that path.
If a basename.ui file is found, it will be processed with the uic tool to
generate the header.
find_package(Qt5 COMPONENTS Widgets Core REQUIRED)
set(CMAKE_AUTOUIC YES)
add_executable(MyQtApp
main.cpp
mainwindow.cpp
mainwindow.h
)
target_link_libraries(MyQtApp PRIVATE
Qt5::Widgets
Qt5::Core
)find_package(Qt5 COMPONENTS Widgets Core REQUIRED)
set(CMAKE_AUTOUIC YES)
add_executable(MyQtApp
main.cpp
mainwindow.cpp
mainwindow.h
)
target_link_libraries(MyQtApp PRIVATE
Qt5::Widgets
Qt5::Core
)
使用 时AUTOUIC,CMake 3.8 及更高版本将ui_*.h在为此类自动生成的文件预留的单独目录中生成文件。其行为与 for 完全相同AUTOMOC,该目录会自动添加到目标的标头搜索路径中。使用 CMake 3.7 或更早版本时,生成的文件是在当前构建目录中创建的,并且该目录不会自动添加到标头搜索路径中。因此,CMAKE_INCLUDE_CURRENT_DIR当必须支持 CMake 3.7 或更早版本时,该变量通常设置为 true。
When using AUTOUIC, CMake 3.8 and later will generate the ui_*.h files
in a separate directory set aside for such auto-generated files.
The behavior is exactly the same as for AUTOMOC, with that directory being
automatically added to the target’s header search path.
When using CMake 3.7 or earlier, the generated files are created in the current
build directory and that directory is not automatically added to the header
search path.
Therefore, the CMAKE_INCLUDE_CURRENT_DIR variable is typically set
to true when CMake 3.7 or earlier must be supported.
与 的另一个相似之处是AUTOMOC,如果由于某种原因不应扫描源文件
AUTOUIC,则可以将其SKIP_AUTOUICsource 属性设置为 true。moc如果该源文件也应被其他 Qt 工具(例如和 )
跳过rcc,SKIP_AUTOGEN则应将 source 属性设置为 true。
In another similarity to AUTOMOC, if a source file should not be scanned by
AUTOUIC for some reason, its SKIP_AUTOUIC source property can be set to
true.
If that source file should also be skipped by other Qt tools like moc and
rcc, the SKIP_AUTOGEN source property should be set to true instead.
在某些项目中,可能需要自定义提供给uic. 例如,该--tr选项可用于指定不同的翻译函数。可以在目标属性中指定此类命令行选项,该属性
在创建目标时AUTOUIC_OPTIONS从变量中获取初始值。CMAKE_AUTOUIC_OPTIONS此外,目标可以uic通过设置目标属性来对链接到它的任何内容传递传递选项INTERFACE_AUTOUIC_OPTIONS。例如,当目标本身提供翻译函数并且希望确保链接到它的库将使用该翻译函数而不是默认函数时,这很有用。CMake 文档在其 Qt 手册中包含此场景的示例。
In some projects, it may be necessary to customize the command-line options
given to uic.
For example, the --tr option can be used to specify a different translation
function.
Such command-line options can be specified in the AUTOUIC_OPTIONS target
property, which takes its initial value from the CMAKE_AUTOUIC_OPTIONS
variable when the target is created.
In addition, a target can enforce uic options transitively on anything that
links to it by setting the INTERFACE_AUTOUIC_OPTIONS target property.
This is useful, for example, when the target itself provides the translation
function and it wants to ensure that libraries that link to it will use that
translation function instead of the default one.
The CMake documentation contains examples of this scenario in its Qt manual.
在大型项目上使用AUTOUIC可能会产生与 类似的性能影响AUTOMOC。如果项目想要避免自动源扫描并.ui
直接指定要处理的文件,可以使用组件提供的宏来实现
Qt5::Widgets:
The use of AUTOUIC on large projects can have similar performance
implications as AUTOMOC.
If a project wants to avoid the automatic source scanning and specify the .ui
files to be processed directly, it can do so with the macro provided by the
Qt5::Widgets component:
qt5_wrap_ui(outVar uiFiles... [OPTIONS ...])qt5_wrap_ui(outVar uiFiles... [OPTIONS ...])
qt5_wrap_cpp()与生成.cpp文件不同,该qt5_wrap_ui()
命令生成标头。在输出变量中提供给调用者的标头列表应直接添加到目标中,以确保应用创建的自定义构建规则
qt5_wrap_ui()。
Unlike qt5_wrap_cpp() which produces .cpp files, the qt5_wrap_ui()
command produces headers.
The list of headers provided back to the caller in the output variable should
be added directly to a target to ensure that the custom build rules created by
qt5_wrap_ui() are applied.
find_package(Qt5 COMPONENTS Widgets Core REQUIRED)
qt5_wrap_ui(genUiHeaders mainwindow.ui)
add_executable(MyQtApp
main.cpp
mainwindow.cpp
mainwindow.h
${genUiHeaders}
)find_package(Qt5 COMPONENTS Widgets Core REQUIRED)
qt5_wrap_ui(genUiHeaders mainwindow.ui)
add_executable(MyQtApp
main.cpp
mainwindow.cpp
mainwindow.h
${genUiHeaders}
)
无论是否AUTOUIC使用,项目都可以选择将
.ui文件直接列为目标的源。这不会影响这些文件的处理方式,但它确实会使这些文件显示在某些 IDE 工具的源列表中。项目甚至可能想要定义一个源代码组来改进某些 IDE 中的组织。例如:
Regardless of whether or not AUTOUIC is used, projects may choose to list
.ui files as sources directly for a target.
This will not affect how those files are processed, but it does make those
files show up in the list of sources in some IDE tools.
Projects may even want to define a source group to improve the organization
within some IDEs.
For example:
source_group("UI Files" REGULAR_EXPRESSION [[.*\.ui]])source_group("UI Files" REGULAR_EXPRESSION [[.*\.ui]])
Qt 资源通常在具有.qrc文件扩展名的文件中定义。此类文件是.qml应用程序将使用 Qt 资源系统访问的其他文件(图像、文件等)的 XML 描述。这些.qrc文件需要由该rcc工具处理,该工具生成将资源嵌入为 C++ 代码的源文件或可在运行时动态加载的二进制文件。
Qt resources are typically defined in files with a .qrc file extension.
Such files are XML descriptions of other files (images, .qml files, etc.)
which the application will access using the Qt resource system.
These .qrc files need to be processed by the rcc tool, which generates
either a source file embedding the resources as C++ code or a binary file
that can be dynamically loaded at run time.
处理文件的最简单方法.qrc是将目标属性设置AUTORCC为 true 并将.qrc文件添加到目标的源列表中。这将导致资源被编码为生成的 C++ 源文件,并AUTORCC自动添加到目标中。项目没有什么可做的,所以这个方法非常简单。
创建目标时,该AUTORCC属性从变量获取初始值。CMAKE_AUTORCC
The simplest way of handling .qrc files is to set the AUTORCC target
property to true and add the .qrc files to the list of sources for the
target.
This will result in the resources being encoded as a generated C++ source
file which AUTORCC automatically adds to the target.
There is nothing more for the project to do, so this method is very
straightforward.
The AUTORCC property takes its initial value from the CMAKE_AUTORCC
variable when the target is created.
set(CMAKE_AUTORCC YES)
add_executable(MyQtApp
main.cpp
myclass.cpp
myclass.h
myApp.qrc
)set(CMAKE_AUTORCC YES)
add_executable(MyQtApp
main.cpp
myclass.cpp
myclass.h
myApp.qrc
)
rcc可以在目标属性中提供 的命令行选项AUTORCC_OPTIONS
,但这通常不是必需的。与其他 autogen 功能类似,AUTORCC可以.qrc通过设置特定文件SKIP_AUTORCC的源文件属性来跳过该文件,或者设置是否也SKIP_AUTOGEN应跳过该文件。mocuic
Command line options for rcc can be provided in the AUTORCC_OPTIONS
target property, but this should not typically be required.
Similar to the other autogen capabilities, AUTORCC can be skipped for a
particular .qrc file by setting its SKIP_AUTORCC source file property,
or by setting SKIP_AUTOGEN if it should be skipped by moc and uic as
well.
如果项目想要创建资源的二进制版本以在运行时动态加载,则AUTORCC无法使用。一个示例场景是,如果应用程序设置启用了需要该资源的功能,则只需将非常大的资源加载到内存中。对于此类用例,该Qt5::Core组件提供以下命令:
If the project wants to create a binary version of the resources to be
dynamically loaded at run time, AUTORCC cannot be used.
An example scenario is where a very large resource only has to be loaded into
memory if an application setting enables a feature that requires it.
For such use cases, the Qt5::Core component provides the following commands:
qt5_add_resources(outVar qrcFiles... [OPTIONS ...])
qt5_add_big_resources(outVar qrcFiles... [OPTIONS ...])
qt5_add_binary_resources(target qrcFiles...
[OPTIONS ...]
[DESTINATION rccFile]
)qt5_add_resources(outVar qrcFiles... [OPTIONS ...])
qt5_add_big_resources(outVar qrcFiles... [OPTIONS ...])
qt5_add_binary_resources(target qrcFiles...
[OPTIONS ...]
[DESTINATION rccFile]
)
该qt5_add_resources()命令提供与 相同的功能AUTORCC。它定义了自定义命令,这些命令采用qrcFiles为输入并生成源文件,其名称将在名为 的变量中提供outVar。正如 一样AUTOMOC,需要将这些源添加到目标中,以确保强制执行自定义构建规则并编译源。不过,通常很少需要此命令,因为它AUTORCC通常更合适(但请参阅本节末尾与统一构建相关的异常)。
The qt5_add_resources() command provides the same capability as AUTORCC.
It defines custom commands that take the qrcFiles as input and produce
source files whose names will be provided in the variable named by outVar.
Just as for AUTOMOC, these sources need to be added to a target to ensure
the custom build rules are enforced and the sources are compiled.
There’s generally little need for this command though, as AUTORCC is usually
more appropriate (but see an exception related to unity builds at the end of
this section).
该qt5_add_big_resources()命令非常相似,通常可以用作qt5_add_resources(). 它的主要区别在于,它不生成需要编译的源代码,而是绕过编译器并直接生成目标文件。这对于非常大的资源最有用,否则会生成巨大的源文件,这些源文件太大而编译器无法处理。生成的目标文件的名称将在名为 的变量中提供outVar。再次,需要将它们添加到目标中,以确保正确定义相关的构建规则。由于内部实现细节,该qt5_add_big_resources()命令仅在使用 CMake 3.9 或更高版本时由 Qt 提供。
The qt5_add_big_resources() command is very similar and can generally be used
as a drop-in replacement for qt5_add_resources().
Its main difference is that instead of generating sources that need to be
compiled, it bypasses the compiler and generates object files directly.
This is most useful for very big resources which would otherwise generate huge
source files that are too large for compilers to process.
The names of the generated object files will be provided in the variable named
by outVar.
Once again, these need to be added to a target to ensure that the relevant
build rules are properly defined.
Due to internal implementation details, the qt5_add_big_resources() command
is only made available by Qt when using CMake 3.9 or later.
该命令提供将
文件编译成单个动态可加载文件qt5_add_binary_resources()的能力。它需要一个名称,命令将创建该名称作为调用的一部分。生成的文件的名称可以使用关键字指定,或者如果省略,则将使用附加
的名称,并将其创建在..qrc.rcctarget.rccDESTINATIONtarget.rccCMAKE_CURRENT_BINARY_DIR
The qt5_add_binary_resources() command provides the ability to compile .qrc
files into a single dynamically loadable .rcc file.
It requires a target name which the command will create as part of the call.
The name of the generated .rcc file can be specified using the DESTINATION
keyword, or if omitted, the name of the target with .rcc appended will be
used and it will be created in the CMAKE_CURRENT_BINARY_DIR.
find_package(Qt5 COMPONENTS Core REQUIRED)
qt5_add_resources(resCompiledIn compiledIn.qrc)
qt5_add_binary_resources(resLoadable runtimeLoadable.qrc
DESTINATION runtimeLoadable.rcc
)
add_executable(MyQtApp
${resCompiledIn}
${CMAKE_CURRENT_BINARY_DIR}/runtimeLoadable.rcc
...
)
set_target_properties(MyQtApp PROPERTIES
RESOURCE
${CMAKE_CURRENT_BINARY_DIR}/runtimeLoadable.rcc
)find_package(Qt5 COMPONENTS Core REQUIRED)
qt5_add_resources(resCompiledIn compiledIn.qrc)
qt5_add_binary_resources(resLoadable runtimeLoadable.qrc
DESTINATION runtimeLoadable.rcc
)
add_executable(MyQtApp
${resCompiledIn}
${CMAKE_CURRENT_BINARY_DIR}/runtimeLoadable.rcc
...
)
set_target_properties(MyQtApp PROPERTIES
RESOURCE
${CMAKE_CURRENT_BINARY_DIR}/runtimeLoadable.rcc
)
请注意上面生成的runtimeLoadable.rcc文件如何仍然添加到MyQtApp目标中。这样它就可以列在目标的RESOURCE属性中,这是确保文件作为目标的一部分安装的要求。有关在 Apple 平台上处理资源的其他方式的相关讨论,请参阅第 23.2 节“应用程序包” 。
Note in the above how the generated runtimeLoadable.rcc file is still added
to the MyQtApp target.
This is so that it can be listed in the target’s RESOURCE property, which
is a requirement for ensuring the file is installed as part of the target.
See Section 23.2, “Application Bundles” for a related discussion of other ways that
resources can be handled on Apple platforms.
由于AUTORCC上面的示例中未使用 ,因此这些.qrc文件也可以添加到提供给 的源列表中add_executable()。这可能会使它们显示在某些 IDE 的源文件列表中,但不会影响 CMake 处理它们的方式。
Since AUTORCC is not used in the above example, the .qrc files could also
be added to the list of sources given to add_executable().
This may make them show up in source file lists in some IDEs but won’t
otherwise affect how CMake treats them.
应该注意的是,该rcc工具生成具有通用符号名称的 C++ 代码。这对统一构建有影响。如果将 生成的多个源rcc添加到同一目标,则应从统一构建中排除这些源,以防止由于重新定义符号而导致编译时错误。AUTORCC这可以通过设置相关的源属性来完成,但是源文件的名称在使用时对项目不可用。使用 Qt 提供的手动命令设置源属性的示例可能如下所示:
It should be noted that the rcc tool generates C++ code with generic symbol
names.
This has implications for unity builds.
If multiple sources generated by rcc are added to the same target, those
sources should be excluded from unity builds to prevent compile-time errors due
to symbols being redefined.
This can be done by setting the relevant source property, but the names of the
source files are not available to the project when AUTORCC is used.
An example of setting the source properties when using the manual commands
provided by Qt instead might look like this:
find_package(Qt5 COMPONENTS Core REQUIRED)
qt5_add_resources(resourceSet1 resourceSet1.qrc)
qt5_add_resources(resourceSet2 resourceSet2.qrc)
add_executable(MyQtApp
${resourceSet1}
${resourceSet2}
...
)
set_source_files_properties(${resourceSet1} ${resourceSet2}
PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE
)find_package(Qt5 COMPONENTS Core REQUIRED)
qt5_add_resources(resourceSet1 resourceSet1.qrc)
qt5_add_resources(resourceSet2 resourceSet2.qrc)
add_executable(MyQtApp
${resourceSet1}
${resourceSet2}
...
)
set_source_files_properties(${resourceSet1} ${resourceSet2}
PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE
)
Qt 的核心部分是它提供本地化文本的能力。通过调用 C++ 代码中的特殊翻译函数来包装字符串,然后lupdate在代码上运行名为的单独工具以生成.ts可以提供给翻译器的单独文件。该工具还可以处理其他文件类型,例如 QML、UI 和资源文件。该.ts文件由翻译人员更新为给定语言的翻译。在某个时候,lrelease会在该文件上运行一个名为的工具.ts来生成最终的.qm翻译文件,该文件随应用程序一起分发。文件.qm格式是一种更高效的格式,仅适用于运行时,而.ts文件则供翻译人员加载到 Qt Linguist 应用程序中。该lupdate工具可以随时重新运行,以重新扫描源并更新
.ts文件进行任何更改,而不会丢失以前翻译的文本。
A core part of Qt is its ability to provide localized text.
Strings are wrapped with a call to a special translation function in the C++
code, then a separate tool named lupdate is run on the code to produce a
separate .ts file which can be given to a translator.
The tool can also process other file types, such as QML, UI and resource files.
The .ts file is updated by the translator with translations for a given
language.
At some point, a tool named lrelease is run on the .ts file to produce a
final .qm translation file which is distributed with the application.
The .qm file format is a more efficient format intended only for run time,
whereas .ts files are intended for translators to load into the Qt Linguist
application.
The lupdate tool can be re-run at any time to rescan the sources and update a
.ts file with any changes without losing previously translated text.
CMake 不提供任何直接帮助来协调上述过程,但该Qt5::LinguistTools组件确实提供了两个 CMake 命令,这使得步骤相当简单。这两个命令中较简单的一个命令采用一个或多个文件的名称.ts,并定义运行lrelease以生成关联.qm
文件的自定义命令:
CMake does not provide any direct assistance with coordinating the above
process, but the Qt5::LinguistTools component does provide two CMake commands
which make the steps fairly straightforward.
The simpler of the two commands takes the names of one or more .ts files and
defines custom commands that run lrelease to generate the associated .qm
files:
qt5_add_translation(outVar tsFiles...)qt5_add_translation(outVar tsFiles...)
项目需要确保至少有一个目标依赖于生成的.qm
文件,以便强制执行构建规则。这可以是专用的自定义目标,也可以是普通的可执行文件或库目标。例如:
Projects need to ensure that at least one target depends on the generated .qm
files so that the build rules are enforced.
This could be a dedicated custom target or it could be an ordinary executable
or library target.
For example:
find_package(Qt5 COMPONENTS LinguistTools Core REQUIRED)
qt5_add_translation(qmFiles
MyQtApp_de_AT.ts
MyQtApp_de_DE.ts
MyQtApp_ja.ts
)
# Choice 1: Use a custom target
add_custom_target(ProcessTranslations DEPENDS ${qmFiles})
# Choice 2: Add to a real target
add_executable(MyQtApp ${qmFiles} ...)find_package(Qt5 COMPONENTS LinguistTools Core REQUIRED)
qt5_add_translation(qmFiles
MyQtApp_de_AT.ts
MyQtApp_de_DE.ts
MyQtApp_ja.ts
)
# Choice 1: Use a custom target
add_custom_target(ProcessTranslations DEPENDS ${qmFiles})
# Choice 2: Add to a real target
add_executable(MyQtApp ${qmFiles} ...)
该qt5_add_translation()命令从不修改.ts文件,它只生成.qm文件。为了创建或更新文件,必须使用组件.ts提供的其他命令
:Qt5::LinguistTools
The qt5_add_translation() command never modifies the .ts files, it only
produces .qm files.
In order to create or update a .ts file, the other command provided by the
Qt5::LinguistTools component must be used:
qt5_create_translation(outVar
[directories...]
[sources...]
[tsFiles...]
[OPTIONS ...]
)qt5_create_translation(outVar
[directories...]
[sources...]
[tsFiles...]
[OPTIONS ...]
)
此命令将创建任何指定的名称tsFiles(如果它们尚不存在)。然后,它将扫描特定sources的以及在 中找到的任何源
directories(默认情况下递归),并tsFiles通过运行该lupdate工具使用任何新的可翻译字符串更新 。然后它将qt5_add_translation()使用文件列表在内部调用.ts
,因此项目不需要自己调用它。如果需要,该OPTIONS关键字可用于指定要传递给命令的其他命令行选项lupdate。
This command will create any of the named tsFiles if they do not already
exist.
It will then scan the specific sources as well as any sources it finds in the
directories (recursively by default) and update the tsFiles with any new
translatable strings by running the lupdate tool.
It will then call qt5_add_translation() internally with the list of .ts
files, so projects do not need to call that themselves.
The OPTIONS keyword can be used to specify additional command-line options to
pass to the lupdate command, if required.
执行源代码扫描时,该lupdate工具会-I
为目录属性中的每个路径提供一个选项INCLUDE_DIRECTORIES。不使用在目标上定义的包含目录,因为源扫描不与任何特定目标关联。该项目需要确保缺少目标包含目录不会改变所看到的可翻译字符串集lupdate(如果需要,
OPTIONS可以使用关键字手动添加带有-I标志的路径)。
When performing the source code scan, the lupdate tool is given an -I
option for each path in the INCLUDE_DIRECTORIES directory property.
Include directories defined on targets are not used, since the source scanning
is not associated with any particular target.
The project needs to ensure that the lack of target include directories won’t
change the set of translatable strings seen by lupdate (if needed, the
OPTIONS keyword can be used to manually add paths with -I flags).
以下示例设置对当前目录及以下目录中所有源的扫描,并.ts根据需要创建或更新单个文件(全部在构建时执行):
The following example sets up scans of all sources in the current directory and
below, creating or updating a single .ts file as appropriate (all performed
at build time):
qt5_create_translation(qmFiles
${CMAKE_CURRENT_LIST_DIR}
MyQtApp_ja.ts
)
add_custom_target(ProcessTranslations DEPENDS ${qmFiles})qt5_create_translation(qmFiles
${CMAKE_CURRENT_LIST_DIR}
MyQtApp_ja.ts
)
add_custom_target(ProcessTranslations DEPENDS ${qmFiles})
.ts只需在将文件移交给翻译人员以更新翻译之前重新扫描源.ts即可更新文件。每次构建时都没有必要重新扫描源代码,因为在.qm
翻译人员更新翻译之前,生成的文件不会更改。在非常大的项目中,这种重新扫描可能会让开发人员在日常工作流程中感到烦恼。大多数时候,调用为现有文件qt5_add_translation()提供文件就足够了。
仅当首次创建文件或开发人员特别想要重新扫描源并更新文件时才需要调用该命令。一种策略是使用 CMake 缓存选项在两种模式之间切换:.qm.tsqt5_create_translation().ts.ts
The .ts files only need to be updated by rescanning the sources just before
the .ts files are handed over to a translator to update the translations.
There’s little point rescanning the sources with every build because the .qm
files generated won’t change until the translations are updated by the
translator.
In very large projects, this rescanning could be annoying for developers in
their everyday workflow.
Most of the time, calling qt5_add_translation() to provide the .qm files
for the existing .ts files is sufficient.
The qt5_create_translation() command only needs to be called when the .ts
files are first created or when the developer specifically wants to rescan the
sources and update the .ts files.
One strategy is to use a CMake cache option to switch between the two modes:
set(tsFiles MyQtApp_ja.ts)
option(ENABLE_TS_RESCAN
"Enable rescanning sources to update .ts files"
ON
)
if(ENABLE_TS_RESCAN)
qt5_create_translation(qmFiles
${CMAKE_CURRENT_LIST_DIR}
${tsFiles}
)
else()
qt5_add_translation(qmFiles ${tsFiles})
endif()
add_custom_target(ProcessTranslations DEPENDS ${qmFiles})set(tsFiles MyQtApp_ja.ts)
option(ENABLE_TS_RESCAN
"Enable rescanning sources to update .ts files"
ON
)
if(ENABLE_TS_RESCAN)
qt5_create_translation(qmFiles
${CMAKE_CURRENT_LIST_DIR}
${tsFiles}
)
else()
qt5_add_translation(qmFiles ${tsFiles})
endif()
add_custom_target(ProcessTranslations DEPENDS ${qmFiles})
这使得开发人员能够控制何时.ts更新文件以及何时支付重新扫描源的费用。
This allows the developer to be in control of when the .ts files will be
updated and when to pay the cost of rescanning the sources.
一个重要的观察结果是该命令就地qt5_create_translation()更新文件,因此构建规则将直接修改源树中的文件。.ts这些文件通常像其他源一样受到版本控制。这是一个罕见的例子,其中构建步骤更新源树中的某些内容是适当的。
An important observation is that the qt5_create_translation() command
updates .ts files in-place, so the build rules would directly modify the
files in the source tree.
These files would typically be under version control just like other sources.
This is a rare example where a build step updating something in the source tree
is appropriate.
安装和打包 Qt 应用程序是一项非常重要的工作,因为主应用程序可执行文件中必须包含各种不同的文件。可能需要安装的不完整列表包括:
Installing and packaging Qt applications is a non-trivial exercise due to the various different files that must be included with the main application executable. An incomplete list of things that may need to be installed includes:
qt.conf文件。
qt.conf file.
应用程序还必须使用正确的 RPATH 设置进行安装,以允许在应用程序运行时找到库和框架(请参阅第26.2.2 节“RPATH”和第 26.2.3 节“Apple 特定目标”中的讨论) 。在 Apple 平台上,应用程序包的独特目录布局进一步使部署复杂化(请参阅第 23 章,Apple 功能)。
The application must also be installed with the right RPATH settings to allow libraries and frameworks to be found when the application is run (see the discussions in Section 26.2.2, “RPATH” and Section 26.2.3, “Apple-specific Targets”). On Apple platforms, the unique directory layout of app bundles further complicates deployment (see Chapter 23, Apple Features).
Qt 提供了一些工具,可以自动执行部署过程中一些较困难的方面。这些工具并非适用于所有平台,但在可用的情况下,它们可以极大地简化安装和打包。它们包括macdeployqt、windeployqt和androiddeployqt。这些工具的基本语法是:
Qt provides tools which automate some of the more difficult aspects of the
deployment process.
These tools are not available for all platforms, but where they are available,
they greatly simplify installation and packaging.
They include macdeployqt, windeployqt and androiddeployqt.
The basic syntax of these tools is:
macdeployqt [选项...] 应用程序包 Windeployqt [选项] 文件... androiddeployqt 选项...
macdeployqt [options...] app-bundle windeployqt [options] files... androiddeployqt options...
每个工具都有自己的一组选项,但总体思路是相同的。他们将必要的 Qt 库、框架、插件和平台文件复制到应用程序包或目录结构中的相关位置。qt.conf他们还可以在适当的位置创建一个简单的文件。命令行选项可用于定制工具的行为,所有这些都在 Qt 文档和工具自身的--help输出中进行了描述。读者应查阅所使用的特定工具的文档,以熟悉其要求和可用选项。
Each tool has its own set of options, but the general idea is the same.
They copy the necessary Qt libraries, frameworks, plugins and platform files
to the relevant location within the application bundle or directory structure.
They may also create a simple qt.conf file in the appropriate location.
Command line options can be used to tailor the tool’s behavior, all of which
are described in the Qt documentation and the tool’s own --help output.
The reader should consult the documentation for the specific tool being used to
familiarize themselves with its requirements and available options.
本节的讨论重点是将这些工具集成到 CMake 项目中的 CMake 特定方面。假设项目已经定义了相关install()
命令并且任何关联的属性都已适当设置。
第 26 章“安装”、第 23 章“Apple 功能”和第 19 章“使用文件”尤其相关。
Discussion here in this section focuses on the CMake-specific aspects of
integrating these tools into a CMake project.
It is assumed that the project already defines the relevant install()
commands and any associated properties are set appropriately.
Chapter 26, Installing, Chapter 23, Apple Features and Chapter 19, Working With Files are especially
relevant.
安装目标和文件后,需要在安装时调用 Qt 部署工具。or命令用于实现逻辑,但如第 26.6 节“自定义安装逻辑”中所述,执行此类安装时代码的时间仅在 CMake 3.14 或更高版本中明确定义install(CODE)。这些命令通常应位于安装应用程序及其文件的目录范围的末尾,位于所有其他命令之后。install(SCRIPT)install()
The Qt deploy tool needs to be invoked at install time after targets and files
have been installed.
The install(CODE) or install(SCRIPT) command is used to implement the
logic, but as noted in Section 26.6, “Custom Install Logic”, the timing of when such
install-time code will be executed is only well-defined with CMake 3.14 or
later.
These commands should generally be at the end of the directory scope where the
application and its files are installed, after all other install() commands.
项目可能会发现使用单独的文件来保存自定义安装代码更易于管理。随着应用程序复杂性的增加,所需的自定义步骤数量可能会增加,将逻辑放在单独的文件中可以避免过多的转义。调用部署工具的相当通用的自定义安装脚本的示例可能如下所示:
Projects may find it more manageable to use a separate file to hold the custom install code. As the application complexity grows, the number of custom steps required may increase and placing the logic in a separate file avoids excessive escaping. An example of a fairly generic custom install script invoking the deployment tool might look like this:
execute_process(
COMMAND "@DEPLOYQT_EXECUTABLE@" @DEPLOY_OPTIONS@
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Executing @DEPLOYQT_EXECUTABLE@ failed: ${result}"
)
endif()execute_process(
COMMAND "@DEPLOYQT_EXECUTABLE@" @DEPLOY_OPTIONS@
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Executing @DEPLOYQT_EXECUTABLE@ failed: ${result}"
)
endif()
该文件预计会使用 复制到构建目录中
configure_file(),并在此过程中替换部署工具及其命令行选项的位置。下面显示了CMakeLists.txt在 macOS 平台上安装应用程序包时可能会查找关联文件的方式(为简洁起见,已省略其他平台的代码):
This file is expected to be copied into the build directory with
configure_file(), substituting the location of the deployment tool and its
command-line options along the way.
The following shows how the associated CMakeLists.txt file might look for
installing an application bundle on the macOS platform (code for other
platforms has been omitted for brevity):
add_executable(MyQtApp MACOSX_BUNDLE WIN32 ...)
set_target_properties(MyQtApp PROPERTIES
INSTALL_RPATH @executable_path/../Frameworks
)
install(TARGETS MyQtApp
BUNDLE DESTINATION .
...
)
get_target_property(mocExe Qt5::moc IMPORTED_LOCATION)
get_filename_component(qtBinDir "${mocExe}" DIRECTORY)
find_program(DEPLOYQT_EXECUTABLE macdeployqt
PATHS "${qtBinDir}"
NO_DEFAULT_PATH
)
set(DEPLOY_OPTIONS [[
MyQtApp.app
-verbose=2
"-codesign=Apple Development"
]])
configure_file(deployapp.cmake.in deployapp.cmake @ONLY)
install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/deployapp.cmake)add_executable(MyQtApp MACOSX_BUNDLE WIN32 ...)
set_target_properties(MyQtApp PROPERTIES
INSTALL_RPATH @executable_path/../Frameworks
)
install(TARGETS MyQtApp
BUNDLE DESTINATION .
...
)
get_target_property(mocExe Qt5::moc IMPORTED_LOCATION)
get_filename_component(qtBinDir "${mocExe}" DIRECTORY)
find_program(DEPLOYQT_EXECUTABLE macdeployqt
PATHS "${qtBinDir}"
NO_DEFAULT_PATH
)
set(DEPLOY_OPTIONS [[
MyQtApp.app
-verbose=2
"-codesign=Apple Development"
]])
configure_file(deployapp.cmake.in deployapp.cmake @ONLY)
install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/deployapp.cmake)
值得注意的是,DEPLOY_OPTIONSformacdeployqt可用于执行诸如处理代码签名(如上例所示)之类的操作,以及跳过因使用私有 API 而不满足应用商店要求的插件。macdeployqt详细信息请参阅帮助。
It is worth noting that the DEPLOY_OPTIONS for macdeployqt can be used
to do things like handle code signing (shown in the above example) and to skip
plugins that do not meet app store requirements due to their use of private
APIs.
See the macdeployqt help for details.
上面的模式也非常相似windeployqt。主要区别是:
The above pattern would be very similar for windeployqt as well.
The main differences would be:
INSTALL_RPATH属性将被忽略。
INSTALL_RPATH target property would be ignored.
find_program()会搜索windeployqt而不是macdeployqt.
find_program() would search for windeployqt instead of macdeployqt.
DEPLOY_OPTIONS将特定于该windeployqt工具。
DEPLOY_OPTIONS would be specific to the windeployqt tool.
该windeployqt工具遵循与 CMake 和GNUInstallDirs模块使用的不同的默认目录布局。特别是,该工具假设应用程序二进制文件位于基本安装位置,而不是位于bin该点下方的子目录中。如果需要,可以使用命令行选项强制该工具生成与 CMake 默认值更加一致的布局。例如,如果MyQtApp前面示例中的目标使用第 26.1 节“目录布局”GNUInstallDirs中描述的标准布局,则可以使用以下命令行选项集:windeployqt
The windeployqt tool follows a different default directory layout to the one
that CMake and the GNUInstallDirs module uses.
In particular, the tool assumes the application binary is at the base install
location instead of in a bin subdirectory below that point.
Command line options can be used to coerce the tool to produce a layout more
closely aligned with CMake’s defaults if required though.
For example, if the MyQtApp target in the earlier example is using the
standard GNUInstallDirs layout described in Section 26.1, “Directory Layout”, the
following set of windeployqt command line options could be used:
set(DEPLOY_OPTIONS [[
bin/MyQtApp.exe
--dir .
--libdir bin
--plugindir plugins
]])set(DEPLOY_OPTIONS [[
bin/MyQtApp.exe
--dir .
--libdir bin
--plugindir plugins
]])
所需的另一个步骤是安装一个qt.conf文件,该文件说明了上述安装的布局更改。它应该安装到与应用程序二进制文件相同的目录中,该目录将位于bin标准目录布局的目录中。该windeployqt工具不会写入qt.conf文件,因此项目必须自己创建一个文件,但只需要一个非常小的文件:
The other step required is to install a qt.conf file that accounts for the
above installed layout changes.
It should be installed to the same directory as the application binary, which
will be in the bin directory for the standard directory layout.
The windeployqt tool doesn’t write a qt.conf file, so the project has to
create one itself, but only a very minimal file is needed:
[Paths]
Prefix = ..[Paths]
Prefix = ..
部署翻译文件与安装任何其他任意文件或资源类似,但 Qt 本身附带的翻译文件需要特殊处理。Qt 5 根据组件拆分其翻译文件,但 Qt 文档建议将应用程序所需的这些文件合并到每种翻译语言的一个文件中以进行部署(请参阅 Qt Linguist 手册的开发人员部分)。qt_xx.qm这使得开发和部署更加容易,因为在运行时只需要加载一个表单文件
来处理 Qt 的翻译。该windeployqt工具会自动处理此问题,并默认创建这些翻译文件,但如果需要,可以将其禁用。该macdeployqt工具不提供此功能,因此项目必须自己实现。Qt 提供的工具lconvert可用于合并必要的文件,这也可以在安装时在
用于调用部署工具的.qm
同一脚本中完成。deployapp.cmake
Deploying translation files is similar to installing any other arbitrary file
or resource, but the translation files that come with Qt itself require special
handling.
Qt 5 splits its translation files based on components, but the Qt
documentation recommends merging those files the application needs into one
file per translated language for deployment (see the developers section of the
Qt Linguist manual).
This makes development and deployment easier because only one file of the form
qt_xx.qm needs to be loaded at runtime to handle Qt’s translations.
The windeployqt tool handles this automatically and will create these
translation files by default, but it can be disabled if desired.
The macdeployqt tool does not offer this functionality, so projects have to
implement it themselves.
The lconvert tool provided by Qt can be used to merge the necessary .qm
files and this can also be done at install time in the same deployapp.cmake
script used to invoke the deployment tool.
以下内容扩展了上一节中的 macOS 示例。它展示了如何将基本语言文件与 Qt Multimedia 模块的翻译结合起来(有关如何在文件中填充变量的信息,请参阅第 31.4 节“翻译”):qmFilesCMakeLists.txt
The following expands on the macOS example from the previous section.
It shows how to combine the base language file with the translations for the
Qt Multimedia module (see Section 31.4, “Translations” for how the qmFiles variable is
populated in the CMakeLists.txt file):
set(outDir "${CMAKE_INSTALL_PREFIX}/@TRANSLATION_DIR@")
set(inDir "@qtBinDir@/../translations")
# Loop over the set of languages to deploy
foreach(lang IN ITEMS ja de fr)
execute_process(
COMMAND "@LCONVERT_EXECUTABLE@"
-o ${outDir}/qt_${lang}.qm
${inDir}/qtbase_${lang}.qm
${inDir}/qtmultimedia_${lang}.qm
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Failed to create qt_${lang}.qm: ${result}"
)
endif()
endforeach()
execute_process(
COMMAND "@DEPLOYQT_EXECUTABLE@" @DEPLOY_OPTIONS@
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Executing @DEPLOYQT_EXECUTABLE@ failed: ${result}"
)
endif()set(outDir "${CMAKE_INSTALL_PREFIX}/@TRANSLATION_DIR@")
set(inDir "@qtBinDir@/../translations")
# Loop over the set of languages to deploy
foreach(lang IN ITEMS ja de fr)
execute_process(
COMMAND "@LCONVERT_EXECUTABLE@"
-o ${outDir}/qt_${lang}.qm
${inDir}/qtbase_${lang}.qm
${inDir}/qtmultimedia_${lang}.qm
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Failed to create qt_${lang}.qm: ${result}"
)
endif()
endforeach()
execute_process(
COMMAND "@DEPLOYQT_EXECUTABLE@" @DEPLOY_OPTIONS@
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}
RESULT_VARIABLE result
)
if(result)
message(FATAL_ERROR
"Executing @DEPLOYQT_EXECUTABLE@ failed: ${result}"
)
endif()
add_executable(MyQtApp MACOSX_BUNDLE WIN32 ...)
set_target_properties(MyQtApp PROPERTIES
INSTALL_RPATH @executable_path/../Frameworks
)
set_source_files_properties(${qmFiles} PROPERTIES
MACOSX_PACKAGE_LOCATION translations
)
install(TARGETS MyQtApp
BUNDLE DESTINATION .
...
)
get_target_property(mocExe Qt5::moc IMPORTED_LOCATION)
get_filename_component(qtBinDir "${mocExe}" DIRECTORY)
find_program(LCONVERT_EXECUTABLE lconvert
PATHS "${qtBinDir}"
NO_DEFAULT_PATH
)
find_program(DEPLOYQT_EXECUTABLE macdeployqt
PATHS "${qtBinDir}"
NO_DEFAULT_PATH
)
set(DEPLOY_OPTIONS [[
MyQtApp.app
-verbose=2
"-codesign=Apple Development"
]])
set(TRANSLATION_DIR MyQtApp.app/Contents/translations)
configure_file(deployapp.cmake.in deployapp.cmake @ONLY)
install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/deployapp.cmake)add_executable(MyQtApp MACOSX_BUNDLE WIN32 ...)
set_target_properties(MyQtApp PROPERTIES
INSTALL_RPATH @executable_path/../Frameworks
)
set_source_files_properties(${qmFiles} PROPERTIES
MACOSX_PACKAGE_LOCATION translations
)
install(TARGETS MyQtApp
BUNDLE DESTINATION .
...
)
get_target_property(mocExe Qt5::moc IMPORTED_LOCATION)
get_filename_component(qtBinDir "${mocExe}" DIRECTORY)
find_program(LCONVERT_EXECUTABLE lconvert
PATHS "${qtBinDir}"
NO_DEFAULT_PATH
)
find_program(DEPLOYQT_EXECUTABLE macdeployqt
PATHS "${qtBinDir}"
NO_DEFAULT_PATH
)
set(DEPLOY_OPTIONS [[
MyQtApp.app
-verbose=2
"-codesign=Apple Development"
]])
set(TRANSLATION_DIR MyQtApp.app/Contents/translations)
configure_file(deployapp.cmake.in deployapp.cmake @ONLY)
install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/deployapp.cmake)
上面的示例显示了如何将翻译文件安装到
translations目录中。假设项目遵循
第 26.1 节“目录布局”QLibraryInfo中建议的标准布局,这与
和文件使用的默认位置相匹配qt.conf。因此,项目不需要提供自己的自定义qt.conf
文件,提供的文件macdeployqt就足够了。请注意,部署工具通常不提供更改其生成的文件的方法
qt.conf。如果项目尝试安装自己的项目,它可能会发出警告,因此最好遵循默认值。
The above example shows how to install the translation files to the
translations directory.
Assuming the project is following the standard layout as recommended in
Section 26.1, “Directory Layout”, this matches the default location used by QLibraryInfo
and qt.conf files.
As a result, the project does not need to provide its own custom qt.conf
file, the one provided by macdeployqt is sufficient.
Note that the deploy tool doesn’t typically provide a way to change the
qt.conf file it produces.
It may issue warnings if the project tries to install its own, so following the
defaults is desirable.
Qt 没有提供 Linux 的官方部署工具。有一个社区支持的linuxdeployqt工具,但它没有得到官方认可,并且与官方 Qt 工具相比存在一些理念和设计差异。如果linuxdeployqt不合适,项目将需要自己处理部署的所有方面。执行此操作的适当方法取决于项目是否将依赖 Linux 发行版来提供 Qt 运行时支持,或者是否将其打包为独立的独立应用程序。
Qt does not provide an official deployment tool for Linux.
There is a community-supported linuxdeployqt tool, but it is not officially
endorsed and has some philosophical and design differences compared to the
official Qt tools.
If linuxdeployqt is not suitable, projects will need to handle all aspects of
deployment themselves.
The appropriate way to do that depends on whether the project will rely on the
Linux distribution to provide Qt runtime support or whether it will be packaged
up as a self-contained, standalone application.
至少从表面上看,依靠 Linux 发行版提供 Qt 运行时支持是最简单的途径。除了打包自己的组件之外,该项目基本上没有什么可做的。如果该项目成为 Linux 发行版的一部分,发行版维护者将添加必要的包依赖信息。这种方法的主要缺点是,当不属于 Linux 发行版时,由于二进制兼容性、目录布局等方面的潜在差异,项目需要为每个发行版创建单独的二进制包。
Relying on the Linux distribution to provide the Qt runtime support is, on the surface at least, the simplest path. There is essentially nothing for the project to do other than package up its own components. If the project is made part of the Linux distribution, the distribution maintainer would add the necessary package dependency information. The main drawback to this approach is that when not part of the Linux distribution, the project would need to create separate binary packages for each distribution due to potential differences in binary compatibility, directory layout, etc.
支持独立的独立打包是一个非常不同的场景。Linux/X11 部署的 Qt 文档详细介绍了需要完成的所有必要的事情,但这是一个不简单的练习。开发人员需要识别该页面中适用于其项目的所有项目,并确保这些项目是其项目安装逻辑的一部分。该逻辑可能还需要考虑 Qt 版本之间库、插件等的任何变化。
Supporting self-contained, standalone packaging is a very different scenario. The Qt documentation for Linux/X11 deployment details all the necessary things that need to be done, but it is a non-trivial exercise. Developers would need to identify all of the items from that page that would apply to their project and ensure those items are part of their project’s install logic. That logic may also need to account for any changes in libraries, plugins, etc. between Qt versions.
如果项目需要支持独立打包和直接包含到 Linux 发行版中,则所有与 Qt 相关的安装逻辑都应以某种 CMake 缓存选项为条件,以简化发行版维护人员的工作。
If the project needs to support both standalone packaging and direct inclusion into Linux distributions, all of the Qt-related install logic should be conditional on some kind of CMake cache option to simplify the work of distribution maintainers.
Qt 5.15 添加了一些功能来帮助项目准备过渡到 Qt 6。除了版本化的导入目标和 CMake 命令之外,还提供未版本化的等效项。对于每个以 开头的目标名称,可以使用Qt5::带有前缀的等效未版本化目标。Qt::同样,每个命令
也qt5_…()有未版本化的等效命令。qt_…()项目可以切换为使用未版本化的目标和命令,这可能允许他们在此过渡期间使用 Qt 5 或 Qt 6 进行构建。
Qt 5.15 added some features to help projects prepare for a transition to
Qt 6.
In addition to the versioned imported targets and CMake commands, unversioned
equivalents are also available.
For each target name starting with Qt5::, an equivalent unversioned target
with the prefix Qt:: can be used.
Similarly, each of the qt5_…() commands have unversioned qt_…()
equivalents as well.
Projects can switch to using the unversioned targets and commands, which may
allow them to build with either Qt 5 or Qt 6 during this period of
transition.
# Use Qt 6 if available, otherwise fall back to Qt 5
set(qtComponents Core Widgets LinguistTools)
find_package(Qt6 COMPONENTS ${qtComponents} QUIET)
if (NOT Qt6_FOUND)
find_package(Qt5 5.15
COMPONENTS ${qtComponents}
REQUIRED
)
endif()
add_executable(MyQtApp ...)
target_link_libraries(MyQtApp PRIVATE Qt::Widgets Qt::Core)# Use Qt 6 if available, otherwise fall back to Qt 5
set(qtComponents Core Widgets LinguistTools)
find_package(Qt6 COMPONENTS ${qtComponents} QUIET)
if (NOT Qt6_FOUND)
find_package(Qt5 5.15
COMPONENTS ${qtComponents}
REQUIRED
)
endif()
add_executable(MyQtApp ...)
target_link_libraries(MyQtApp PRIVATE Qt::Widgets Qt::Core)
读者应该意识到,对于是否建议使用未版本化的目标和命令存在不同的观点(请参阅QTBUG-83774)。在采用上述过渡方法之前,每个项目都需要考虑这些问题对他们来说是否重要。
The reader should be aware that there are differing views on whether using unversioned targets and commands is advisable (see QTBUG-83774). Each project will need to consider whether these concerns are important to them before adopting the above transitional approach.
项目应该已经从 Qt 4 迁移并使用 Qt 5,并准备很快支持 Qt 6。对 Qt 的 CMake 支持的开发主要集中在 Qt 5 上,最近又集中在 Qt 6 上。继续使用 Qt 4 会出现越来越多的问题,因此不建议这样做。对于使用 Qt 6 的项目,更喜欢使用 CMake 3.19 或更高版本(静态构建为 3.21 或更高版本),以利用一系列与 Qt 相关的新 CMake 功能和错误修复。
Projects should already have migrated off Qt 4 and be using Qt 5, with preparations for supporting Qt 6 soon. Development of CMake support for Qt is focused on Qt 5 and more recently on Qt 6. Staying with Qt 4 will become increasingly problematic and is not recommended. For projects working with Qt 6, prefer to use CMake 3.19 or later (3.21 or later for static builds) to take advantage of a range of new CMake features and bug fixes relevant to Qt.
当使用 Qt 将 Qt 引入项目时find_package(),更愿意找到Qt5
伞包并在该调用中指定COMPONENTS,而不是查找每个组件作为其自己的单独find_package()调用。这可以更清楚地传达意图并且更稳健,因为它确保所有 Qt 组件都来自相同的 Qt 安装。
When bringing Qt into a project with find_package(), prefer to find the Qt5
umbrella package and specify COMPONENTS in that one call rather than finding
each component as its own separate find_package() call.
This communicates the intent more clearly and is more robust, since it ensures
all Qt components come from the same Qt installation.
避免通过变量引用 Qt 提供的东西。相反,更喜欢使用导入的目标,例如Qt5::Core、Qt5::Gui等。导入的目标更加健壮并且使用起来更加方便,因为它们具有传递属性,可以极大地简化定义链接到 Qt 库的目标的任务。
Avoid referring to things Qt provides through variables.
Instead, prefer to use the imported targets like Qt5::Core, Qt5::Gui, etc.
The imported targets are much more robust and more convenient to use, as they
come with transitive properties that greatly simplify the task of defining
targets that link against Qt libraries.
项目应该更喜欢使用AUTOMOC,AUTOUIC和AUTORCCQt 提供的手动命令。该AUTOxxx功能相当简单,项目文件也更加简洁。由于在项目的生命周期中添加、修改、重命名和移动文件,因此维护起来也更容易。只有当明确对构建性能的影响需要额外的努力或不支持所需的处理(例如动态可加载资源)时,才应使用 Qt 提供的手动命令。
Projects should prefer to use AUTOMOC, AUTOUIC and AUTORCC over the
manual commands provided by Qt.
The AUTOxxx functionality is considerably simpler and results in more concise
project files.
It is also easier to maintain as files are added, modified, renamed and moved
over the course of a project’s lifetime.
Only where it is clear that the impact on build performance warrants the extra
effort or where the desired processing isn’t supported (e.g. dynamically
loadable resources) should the manual commands provided by Qt be used.
将翻译文件定义为项目的一部分时,最好遵循 Qt Linguist 使用的相同默认命名约定。理想情况下,翻译文件应根据模式命名
something_xx_YY.ts,其中xx是小写 ISO639 语言代码,YY
是大写 ISO3166 国家/地区代码。_YY如果不需要基于国家/地区的本地化,则可以省略该部分。避免在名称中使用多个句点字符。特别是,不要使用 形式的名称something.xx_YY.ts,即使 Qt 文档中的一些示例这样做。在 Qt 5.12.5 之前,此类名称可能会在执行诸如qt5_create_translation()和 之类的命令时触发错误qt5_add_translation(),当存在多个.ts文件时,这可能会导致构建失败。
When defining translation files to be part of the project, prefer to follow the
same default naming convention that Qt Linguist uses.
Translation files should ideally be named according to the pattern
something_xx_YY.ts, where xx is a lowercase ISO639 language code and YY
is an uppercase ISO3166 country code.
The _YY part can be omitted if country-based localization isn’t needed.
Avoid using more than one period character in the name.
In particular, do not use names of the form something.xx_YY.ts, even though
some examples in the Qt documentation do this.
Prior to Qt 5.12.5, such names can trigger a bug in the implementation of
commands like qt5_create_translation() and qt5_add_translation() which can
lead to build failures when there is more than one .ts file.
首选使用 Qt 提供的可用部署工具。macdeployqt和windeployqt等工具androiddeployqt可以处理部署应用程序时的许多 Qt 特定方面,使项目不必维护不同平台和 Qt 版本的逻辑。对于支持本地化的项目,请确保 Qt 的.qm文件也包含在部署中,并且应用程序知道在哪里可以找到它们。.qm所有部署工具并未涵盖对这些 Qt 提供的文件的处理,因此项目可能需要自己实现。
Prefer to use the deployment tools provided by Qt where they are available.
Tools like macdeployqt, windeployqt and androiddeployqt handle much of
the Qt-specific aspects of deploying an application, freeing the project from
having to maintain logic for the different platforms and Qt versions.
For projects that support localization, ensure that Qt’s .qm files are also
included in the deployment and that the application knows where to find them.
Handling of these Qt-provided .qm files is not covered by all deployment
tools, so projects may need to implement that for themselves.
如果为 Qt GUI 应用程序创建包,请考虑使用 IFW 包生成器,它使用 Qt 安装程序框架。可以在所有桌面平台上提供相同的最终用户体验,可以自定义外观以匹配应用程序,并且支持本地化。
If creating packages for a Qt GUI application, consider using the IFW package generator, which uses the Qt Installer Framework. The same end user experience can be provided across all desktop platforms, the look and feel can be customized to match the application, and localization is supported.